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}