001package arcommon.xml;
002
003import java.io.File;
004import java.io.ObjectInputStream;
005import java.io.ObjectOutputStream;
006import java.io.Serializable;
007import java.lang.reflect.Method;
008import java.text.SimpleDateFormat;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.Date;
012import java.util.HashMap;
013import java.util.HashSet;
014import java.util.LinkedHashMap;
015import java.util.LinkedHashSet;
016import java.util.Map;
017
018import arcommon.ReflectionUtil;
019import arcommon.StringUtil;
020
021public class XmlSerializerHelperUtil {
022    public static final int XML_VERSION_NUMBER = 1;
023
024    public static final SimpleDateFormat XML_DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
025
026    public static final String TAG_OBJECT = "Object";
027    public static final String TAG_FIELD = "Field";
028    public static final String TAG_ENTRY = "Entry";
029    public static final String TAG_NULL_ELEMENT = "NullElement";
030    public static final String TAG_SPECIAL_HANDLING = "SpecialHandling";
031    public static final String TAG_DATA = "Data";
032    public static final String TAG_XML_SERIALIZER = "XmlSerializer";
033    
034    public static final String ATTRIBUTE_SERIAL_VERSION_UID = "serialVersionUID";
035    public static final String ATTRIBUTE_SUPER_CLASS = "superClass";
036    public static final String ATTRIBUTE_CLASS = "class";
037    public static final String ATTRIBUTE_NAME = "name";
038    public static final String ATTRIBUTE_LENGTH = "length";
039    public static final String ATTRIBUTE_ID = "id";
040    public static final String ATTRIBUTE_REFERENCE = "reference";
041    public static final String ATTRIBUTE_VALUE = "value";
042    
043    public static final String ATTRIBUTE_DATA_OBJECT_IS_NULL = "dataObjectIsNull";
044    
045    public static final String ATTRIBUTE_KEY_IS_NULL = "keyIsNull";
046    public static final String ATTRIBUTE_KEY_PREFIX = "key";
047    public static final String ATTRIBUTE_KEY_CLASS = ATTRIBUTE_KEY_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_CLASS);
048    public static final String ATTRIBUTE_KEY_LENGTH = ATTRIBUTE_KEY_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_LENGTH);
049    public static final String ATTRIBUTE_KEY_ID = ATTRIBUTE_KEY_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_ID);
050    public static final String ATTRIBUTE_KEY_REFERENCE = ATTRIBUTE_KEY_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_REFERENCE);
051    
052    public static final String ATTRIBUTE_VALUE_IS_NULL = "valueIsNull";
053    public static final String ATTRIBUTE_VALUE_PREFIX = "value";
054    public static final String ATTRIBUTE_VALUE_CLASS = ATTRIBUTE_VALUE_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_CLASS);
055    public static final String ATTRIBUTE_VALUE_LENGTH = ATTRIBUTE_VALUE_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_LENGTH);
056    public static final String ATTRIBUTE_VALUE_ID = ATTRIBUTE_VALUE_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_ID);
057    public static final String ATTRIBUTE_VALUE_REFERENCE = ATTRIBUTE_VALUE_PREFIX + StringUtil.firstCharUpperCase(ATTRIBUTE_REFERENCE);
058
059    public static final String ATTRIBUTE_VALUE_TRUE = "true";
060    
061    public static final String ATTRIBUTE_VERSION = "version";
062
063    public static final String FIELD_NAME_SERIAL_VERSION_UID = "serialVersionUID";
064    
065    public static final String METHOD_NAME_READ_OBJECT = "readObject";
066    public static final String METHOD_NAME_WRITE_OBJECT = "writeObject";
067    
068    public static final String ENUM_NULL = "null";
069    
070    public static boolean handleCollectionAndMapSpecially = true;
071    /*package*/ enum SpeciallyHandlingType {NONE, COLLECTION, MAP, NEVER_IN_HIERARCHY};
072    private static Map<Class<?>, SpeciallyHandlingType> speciallyHandlingTypeForHierarchyCache = new HashMap<Class<?>, SpeciallyHandlingType>();
073    private static Map<Class<?>, SpeciallyHandlingType> speciallyHandlingTypeCache = new HashMap<Class<?>, SpeciallyHandlingType>();
074    
075    static {
076        /* HACK to avoid the following warning on Open JDK version 11:
077         *   WARNING: An illegal reflective access operation has occurred
078         *   WARNING: Illegal reflective access by arcommon.ReflectionUtil (file:/home/allan/Mit/Java/AR%20Common/AR%20Common/bin/) to field java.util.HashMap.serialVersionUID
079         *   WARNING: Please consider reporting this to the maintainers of arcommon.ReflectionUtil
080         *   WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
081         *   WARNING: All illegal access operations will be denied in a future release
082         */
083        ReflectionUtil.openPackageForModuleAccess(ArrayList.class, XmlSerializerHelperUtil.class);
084        ReflectionUtil.openPackageForModuleAccess(HashMap.class, XmlSerializerHelperUtil.class);
085        ReflectionUtil.openPackageForModuleAccess(HashSet.class, XmlSerializerHelperUtil.class);
086        ReflectionUtil.openPackageForModuleAccess(LinkedHashMap.class, XmlSerializerHelperUtil.class);
087        ReflectionUtil.openPackageForModuleAccess(LinkedHashSet.class, XmlSerializerHelperUtil.class);
088        ReflectionUtil.openPackageForModuleAccess(File.class, XmlSerializerHelperUtil.class);
089        
090//        ReflectionUtil.openPackageForModuleAccess(ZoneInfo.class, XmlSerializer.class); // The type sun.util.calendar.ZoneInfo is not accessible (For some unknown reason) => Use reflection
091        Class<?> classInPackage = ReflectionUtil.loadClass("sun.util.calendar.ZoneInfo");
092        ReflectionUtil.openPackageForModuleAccess(classInPackage, XmlSerializer.class);
093    }
094    
095    /*package*/ static SpeciallyHandlingType calculateSpeciallyHandlingTypeForHierarchy(Class<?> clazz) {
096        SpeciallyHandlingType result = speciallyHandlingTypeForHierarchyCache.get(clazz);
097        if (result == null) {
098            result = calculateSpeciallyHandlingTypeForHierarchyUncached(clazz);
099            speciallyHandlingTypeForHierarchyCache.put(clazz, result);
100        }
101        return result;
102    }
103    
104    private static SpeciallyHandlingType calculateSpeciallyHandlingTypeForHierarchyUncached(Class<?> clazz) {
105        if (!handleCollectionAndMapSpecially) {
106            return SpeciallyHandlingType.NONE;
107        }
108        
109        SpeciallyHandlingType speciallyHandlingType = calculateSpeciallyHandlingType(clazz);
110        if (speciallyHandlingType == SpeciallyHandlingType.NEVER_IN_HIERARCHY) {
111            return SpeciallyHandlingType.NONE;
112        }
113        if (speciallyHandlingType != SpeciallyHandlingType.NONE) {
114            return speciallyHandlingType;
115        }
116        
117        Class<?> superclass = clazz.getSuperclass();
118        if (superclass == Object.class) {
119            return SpeciallyHandlingType.NONE;
120        }
121        
122        return calculateSpeciallyHandlingTypeForHierarchy(superclass);
123    }
124
125    /*package*/ static SpeciallyHandlingType calculateSpeciallyHandlingType(Class<?> clazz) {
126        SpeciallyHandlingType result = speciallyHandlingTypeCache.get(clazz);
127        if (result == null) {
128            result = calculateSpeciallyHandlingTypeUncached(clazz);
129            speciallyHandlingTypeCache.put(clazz, result);
130        }
131        return result;
132    }
133    
134    private static SpeciallyHandlingType calculateSpeciallyHandlingTypeUncached(Class<?> clazz) {
135        if (!handleCollectionAndMapSpecially) {
136            return SpeciallyHandlingType.NONE;
137        }
138        
139        Package package1 = clazz.getPackage();
140        if (package1 == null) {
141            return SpeciallyHandlingType.NONE;
142        }
143        String packageName = package1.getName();
144        if (!packageName.startsWith("java.util")) {
145            return SpeciallyHandlingType.NONE;
146        }
147
148        if (!hasClassADeclaredConstructor(clazz)) {
149            // This will filter out classes like the Arrays.ArrayList class because it is private class, and
150            // the ReflectionUtil.createInstanceDefaultPrivateConstructor(clazz) and the Arrays.ArrayList.add calls will fail.
151            // So instances of this class (and similar) must be handled the normal way (Less human-readable).
152            return SpeciallyHandlingType.NEVER_IN_HIERARCHY;
153        }
154
155        if (Collection.class.isAssignableFrom(clazz)) {
156            return SpeciallyHandlingType.COLLECTION;
157        }
158        
159        if (Map.class.isAssignableFrom(clazz)) {
160            return SpeciallyHandlingType.MAP;
161        }
162        
163        return SpeciallyHandlingType.NONE;
164    }
165    
166    private static boolean hasClassADeclaredConstructor(Class<?> clazz) {
167        try {
168            clazz.getDeclaredConstructor();
169            return true;
170        } catch (NoSuchMethodException e) {
171            return false;
172        } catch (SecurityException e) {
173            throw new RuntimeException(e);
174        }
175    }
176    
177    /*package*/ static boolean shouldObjectHaveAnId(Object object) {
178        return shouldClassHaveAnId(object.getClass());
179    }
180    
181    // Must be a class and not an object, because object.getClass() can't be a small simple class (e.g. boolean)
182    /*package*/ static boolean shouldClassHaveAnId(Class<?> clazz) {
183        if (ReflectionUtil.isClassEnum(clazz)) {
184            return false;
185        }
186        
187        if (isSimpleClass(clazz)) {
188            if (isSimpleLargeClass(clazz)
189                    || isSimpleArray(clazz)) {
190                return true;
191            }
192            
193            return false;
194        }
195        
196        return true;
197    }
198
199    /*package*/ static boolean isObjectOfSimpleClass(Object object) {
200        if (object == null) {
201            return false;
202        }
203        return isSimpleClass(object.getClass());
204    }
205    
206    private static Map<Class<?>, Boolean> isSimpleClassMap = new HashMap<Class<?>, Boolean>();
207    
208    /*package*/ static boolean isSimpleClass(Class<?> clazz) {
209        Boolean isSimpleClass = isSimpleClassMap.get(clazz);
210        if (isSimpleClass == null) {
211            isSimpleClass = isSimpleNonStringClass(clazz)
212                || clazz == String.class
213                || clazz == Character.class || clazz == char.class
214                || clazz == Class.class
215                || isSimpleArray(clazz);
216            isSimpleClassMap.put(clazz, isSimpleClass);
217        }
218        return isSimpleClass.booleanValue();
219    }
220    
221    /*package*/ static boolean isSimpleArray(Class<?> clazz) {
222        if (!clazz.isArray()) {
223            return false;
224        }
225        Class<?> componentType = clazz.getComponentType();
226        if (ReflectionUtil.isClassEnum(componentType)) {
227            return true;
228        }
229        return isSimpleSmallNonStringClass(componentType);
230    }
231    
232    private static boolean isSimpleNonStringClass(Class<?> clazz) {
233        return isSimpleSmallNonStringClass(clazz) || isSimpleLargeNonStringClass(clazz);
234    }
235    
236    /*package*/ static boolean isSimpleSmallClass(Class<?> clazz) {
237        return clazz == char.class || isSimpleSmallNonStringClass(clazz);
238    }
239    
240    private static boolean isSimpleSmallNonStringClass(Class<?> clazz) {
241        return clazz == long.class
242            || clazz == int.class
243            || clazz == short.class
244            || clazz == byte.class
245            || clazz == boolean.class
246            || clazz == double.class
247            || clazz == float.class;
248    }
249    
250    private static boolean isSimpleLargeClass(Class<?> clazz) {
251        return clazz == String.class || clazz == Character.class || isSimpleLargeNonStringClass(clazz);
252    }
253    
254    private static boolean isSimpleLargeNonStringClass(Class<?> clazz) {
255        return clazz == Long.class
256            || clazz == Integer.class
257            || clazz == Short.class
258            || clazz == Byte.class
259            || clazz == Date.class
260            || clazz == Boolean.class
261            || clazz == Double.class
262            || clazz == Float.class
263            || ReflectionUtil.isClassEnum(clazz);
264    }
265    
266    /*package*/ static String enumArrayToString(Object[] objectArray) {
267        StringBuilder stringBuilder = new StringBuilder();
268        for (Object object : objectArray) {
269            if (stringBuilder.length() > 0) {
270                stringBuilder.append(", ");
271            }
272            if (object == null) {
273                stringBuilder.append(XmlSerializerHelperUtil.ENUM_NULL);
274                continue;
275            }
276            String enumToString = ReflectionUtil.enumToStringUnsafe(object);
277            stringBuilder.append(enumToString);
278        }
279        
280        return stringBuilder.toString();
281    }
282    
283    /*package*/ static boolean isSimpleClassDefaultValue(Object value) {
284        if (value instanceof Long && (Long)value == 0) {
285            return true;
286        }
287        if (value instanceof Integer && (Integer)value == 0) {
288            return true;
289        }
290        if (value instanceof Short && (Short)value == 0) {
291            return true;
292        }
293        if (value instanceof Byte && (Byte)value == 0) {
294            return true;
295        }
296        if (value instanceof Character && (Character)value == 0) {
297            return true;
298        }
299        if (value instanceof Boolean && (Boolean)value == false) {
300            return true;
301        }
302        if (value instanceof Double && (Double)value == 0.0D) {
303            return true;
304        }
305        if (value instanceof Float && (Float)value == 0.0F) {
306            return true;
307        }
308        return false;
309    }
310    
311    public static boolean isAssignableFrom(Class<?> fieldClass, Class<?> valueClass) {
312        boolean assignableFrom = fieldClass.isAssignableFrom(valueClass);
313        if (assignableFrom) {
314            return assignableFrom;
315        }
316        
317        if (fieldClass.isPrimitive() || valueClass.isPrimitive()) {
318            if (fieldClass.isPrimitive()) {
319                Class<?> temp = fieldClass;
320                fieldClass = valueClass;
321                valueClass = temp;
322            }
323            
324            if (fieldClass == Long.class && valueClass == long.class) {
325                return true;
326            }
327            if (fieldClass == Integer.class && valueClass == int.class) {
328                return true;
329            }
330            if (fieldClass == Short.class && valueClass == short.class) {
331                return true;
332            }
333            if (fieldClass == Byte.class && valueClass == byte.class) {
334                return true;
335            }
336            if (fieldClass == Boolean.class && valueClass == boolean.class) {
337                return true;
338            }
339            if (fieldClass == Double.class && valueClass == double.class) {
340                return true;
341            }
342            if (fieldClass == Float.class && valueClass == float.class) {
343                return true;
344            }
345            if (fieldClass == Character.class && valueClass == char.class) {
346                return true;
347            }
348        }
349        
350        return false;
351    }
352    
353    
354    private static Map<Class<?>, Class<?>> superSerializableClazzMap = new HashMap<Class<?>, Class<?>>();
355    
356    /*package*/ static Class<?> findSuperSerializableClazz(Class<?> clazz) {
357        Class<?> result = superSerializableClazzMap.get(clazz);
358        if (result == null) {
359            Class<?> tempClazz = clazz;
360            Class<?> superSerializableClazz = null;
361            while (tempClazz != Object.class) {
362                boolean implementsSerializable = isImplementingSerializable(tempClazz);
363                if (implementsSerializable) {
364                    superSerializableClazz = tempClazz;
365                }
366                tempClazz = tempClazz.getSuperclass();
367            }
368            result = superSerializableClazz;
369            superSerializableClazzMap.put(clazz, result);
370        }
371        
372        return result;
373    }
374
375    /*package*/ static boolean isImplementingSerializable(Class<?> clazz) {
376        Class<?>[] interfaces = clazz.getInterfaces();
377        for (int i = 0; i < interfaces.length; i++) {
378            if (interfaces[i] == Serializable.class) {
379                return true;
380            }
381            boolean implementsSerializable = isImplementingSerializable(interfaces[i]);
382            if (implementsSerializable) {
383                return implementsSerializable;
384            }
385        }
386        return false;
387    }
388    
389    /*package*/ static Long getSerialVersionUID(Class<?> clazz) {
390        Object serialVersionUID = ReflectionUtil.getStaticFieldValue(clazz, FIELD_NAME_SERIAL_VERSION_UID, false); // false = Ignore if the field is missing.
391        if (serialVersionUID == null) {
392            return null;
393        }
394        if (!(serialVersionUID instanceof Long)) {
395            throw new RuntimeException("!(serialVersionUID instanceof Long) for " + clazz);
396        }
397        return (Long)serialVersionUID;
398    }
399    
400    /*package*/ static Method findReadMethod(Class<?> clazz) {
401        return ReflectionUtil.findMethod(clazz, METHOD_NAME_READ_OBJECT, new Class[] {ObjectInputStream.class}, true, false);
402    }
403    
404    /*package*/ static Method findWriteMethod(Class<?> clazz) {
405        return ReflectionUtil.findMethod(clazz, METHOD_NAME_WRITE_OBJECT, new Class[] {ObjectOutputStream.class}, true, false);
406    }
407}