| 1 | package org.jtoolkit.essence.app.pojo.impl; |
| 2 | |
| 3 | import org.apache.commons.logging.Log; |
| 4 | import static org.apache.commons.logging.LogFactory.getLog; |
| 5 | import org.jetbrains.annotations.NotNull; |
| 6 | import org.jetbrains.annotations.Nullable; |
| 7 | import org.jtoolkit.essence.app.pojo.DataValue; |
| 8 | import org.jtoolkit.essence.app.pojo.Datable; |
| 9 | import org.jtoolkit.essence.app.pojo.DatableUtils; |
| 10 | import org.jtoolkit.essence.concurrency.Immutable; |
| 11 | import org.jtoolkit.essence.data.Mapping; |
| 12 | import org.jtoolkit.essence.utils.Named; |
| 13 | import org.jtoolkit.essence.utils.Pair; |
| 14 | import org.jtoolkit.essence.utils.StringUtils; |
| 15 | import static org.jtoolkit.essence.utils.StringUtils.*; |
| 16 | import org.jtoolkit.essence.utils.impl.MapArray; |
| 17 | |
| 18 | import java.io.DataInput; |
| 19 | import java.io.DataOutput; |
| 20 | import java.io.IOException; |
| 21 | import java.io.Serializable; |
| 22 | import java.lang.reflect.*; |
| 23 | import java.math.BigDecimal; |
| 24 | import java.math.BigInteger; |
| 25 | import java.util.*; |
| 26 | import static java.util.Arrays.asList; |
| 27 | import java.util.concurrent.ConcurrentHashMap; |
| 28 | |
| 29 | /* |
| 30 | Copyright 2006 Peter Lawrey |
| 31 | |
| 32 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 33 | you may not use this file except in compliance with the License. |
| 34 | You may obtain a copy of the License at |
| 35 | |
| 36 | http://www.apache.org/licenses/LICENSE-2.0 |
| 37 | |
| 38 | Unless required by applicable law or agreed to in writing, software |
| 39 | distributed under the License is distributed on an "AS IS" BASIS, |
| 40 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 41 | See the License for the specific language governing permissions and |
| 42 | limitations under the License. |
| 43 | */ |
| 44 | |
| 45 | /** |
| 46 | * Helper class for building and decomposing data value objects. |
| 47 | * |
| 48 | * @author Peter Lawrey |
| 49 | */ |
| 50 | @Immutable |
| 51 | @SuppressWarnings({"unchecked", "RedundantCast", "ClassWithTooManyMethods", "OverlyComplexClass", "OverlyCoupledClass"}) |
| 52 | public class DataValueClass<T> implements Datable { |
| 53 | private static final Log LOG = getLog(DataValueClass.class); |
| 54 | private static final Map<Object, DataValueClass> pojoClasses = new ConcurrentHashMap<Object, DataValueClass>(256, 0.5f, 16); |
| 55 | public static final Class[] BASE_CLASSES = { |
| 56 | Boolean.class, Byte.class, Character.class, Short.class, Integer.class, Float.class, Double.class, Long.class, |
| 57 | String.class, Class.class, DataValueClass.class, BigInteger.class, BigDecimal.class, Date.class |
| 58 | }; |
| 59 | private static final String[] CLASS_SEP = ",.,$,.data.".split(","); |
| 60 | private static final Map<Class, Class> PRIMATIVE_MATCH = new LinkedHashMap<Class, Class>(); |
| 61 | private static final String[] NO_STRINGS = {}; |
| 62 | private static final Class[] STRING_CLASS = {String.class}; |
| 63 | |
| 64 | static { |
| 65 | PRIMATIVE_MATCH.put(long.class, Long.class); |
| 66 | PRIMATIVE_MATCH.put(int.class, Integer.class); |
| 67 | PRIMATIVE_MATCH.put(double.class, Double.class); |
| 68 | PRIMATIVE_MATCH.put(short.class, Short.class); |
| 69 | PRIMATIVE_MATCH.put(byte.class, Byte.class); |
| 70 | PRIMATIVE_MATCH.put(float.class, Float.class); |
| 71 | PRIMATIVE_MATCH.put(boolean.class, Boolean.class); |
| 72 | PRIMATIVE_MATCH.put(char.class, Character.class); |
| 73 | } |
| 74 | |
| 75 | private static final Map<String, Class> CLASS_LOOKUP = new ConcurrentHashMap<String, Class>(); |
| 76 | |
| 77 | static { |
| 78 | for (Class clazz : PRIMATIVE_MATCH.keySet()) |
| 79 | CLASS_LOOKUP.put(clazz.getName(), clazz); |
| 80 | CLASS_LOOKUP.put(void.class.getName(), void.class); |
| 81 | } |
| 82 | |
| 83 | private static final Set<Class> BASE_CLASS_SET = new LinkedHashSet<Class>(); |
| 84 | |
| 85 | static { |
| 86 | BASE_CLASS_SET.addAll(asList(BASE_CLASSES)); |
| 87 | BASE_CLASS_SET.addAll(PRIMATIVE_MATCH.keySet()); |
| 88 | } |
| 89 | |
| 90 | private final Class<T> type; |
| 91 | private final String[] fields; |
| 92 | private final Map<String, Integer> fieldOrder; |
| 93 | @Nullable private final Map<String, Field> fieldMap; |
| 94 | @Nullable private final Map<String, Method> getterMap; |
| 95 | @Nullable private final Constructor<T> cons; |
| 96 | private final Set<String> mandatoryFields; |
| 97 | private final boolean notSerializable; |
| 98 | private static final String FIELD = "Field "; |
| 99 | |
| 100 | @NotNull public static <T> DataValueClass<T> acquire(@NotNull Class<T> clazz) { |
| 101 | if (clazz == void.class) |
| 102 | throw new IllegalArgumentException("There is no data value class for void."); |
| 103 | |
| 104 | DataValueClass<T> ret = pojoClasses.get(clazz); |
| 105 | if (ret == null) |
| 106 | pojoClasses.put(clazz, ret = new DataValueClass(clazz)); |
| 107 | return ret; |
| 108 | } |
| 109 | |
| 110 | @NotNull private static <T> DataValueClass<T> acquire(@NotNull Class<T> clazz, @NotNull String name) { |
| 111 | Pair key = new Pair(clazz, name); |
| 112 | DataValueClass<T> ret = pojoClasses.get(key); |
| 113 | if (ret == null) |
| 114 | pojoClasses.put(key, ret = new DataValueClass(clazz, name)); |
| 115 | return ret; |
| 116 | } |
| 117 | |
| 118 | @NotNull public static <T> Object[] asArray(@NotNull T dataValue) { |
| 119 | return acquire((Class<T>) dataValue.getClass()).asArray2(dataValue); |
| 120 | } |
| 121 | |
| 122 | @NotNull private Object[] asArray2(@NotNull T dataValue) { |
| 123 | Object[] ret = new Object[fieldOrder.size()]; |
| 124 | for (String field : fieldOrder.keySet()) { |
| 125 | int order = fieldOrder.get(field); |
| 126 | try { |
| 127 | Method getter = getterMap.get(field); |
| 128 | if (getter != null) { |
| 129 | ret[order] = getter.invoke(dataValue); |
| 130 | continue; |
| 131 | } |
| 132 | Field f = fieldMap.get(field); |
| 133 | ret[order] = f.get(dataValue); |
| 134 | } catch (InvocationTargetException e) { |
| 135 | ret[order] = e.getCause(); |
| 136 | } catch (Exception e) { |
| 137 | ret[order] = e; |
| 138 | } |
| 139 | } |
| 140 | return ret; |
| 141 | } |
| 142 | |
| 143 | @NotNull public static <T> Map<String, Object> asMap(@NotNull T dataValue) { |
| 144 | DataValueClass<T> dataValueClass = acquire((Class<T>) dataValue.getClass()); |
| 145 | return dataValueClass.asMap2(dataValue); |
| 146 | } |
| 147 | |
| 148 | @NotNull public Map<String, Object> asMap2(@NotNull T dataValue) { |
| 149 | if (fieldMap == null) |
| 150 | return new MapArray<String, Object>(fieldOrder, new Object[]{dataValue}); |
| 151 | return new MapArray<String, Object>(fieldOrder, asArray2(dataValue)); |
| 152 | } |
| 153 | |
| 154 | @NotNull |
| 155 | public T build(@NotNull Map<String, ? extends Object> map, @NotNull PojoContext pojoContext) throws InstantiationException, IllegalArgumentException { |
| 156 | if (fields.length == 0) throw new IllegalArgumentException("Not fields for " + type); |
| 157 | Object[] objs = new Object[fields.length]; |
| 158 | for (int i = 0; i < fields.length; i++) |
| 159 | objs[i] = map.get(fields[i]); |
| 160 | return build(objs, pojoContext); |
| 161 | } |
| 162 | |
| 163 | @NotNull |
| 164 | public T build(@NotNull Object[] values, @NotNull PojoContext pojoContext) throws InstantiationException, IllegalArgumentException { |
| 165 | // plain data type. |
| 166 | if (fieldMap == null) |
| 167 | return cast(type, values[0], pojoContext); |
| 168 | if (cons != null) { |
| 169 | Object[] values2 = new Object[values.length]; |
| 170 | for (int i = 0; i < fields.length; i++) { |
| 171 | String fieldName = fields[i]; |
| 172 | Field field = fieldMap.get(fieldName); |
| 173 | Class<?> type = field.getType(); |
| 174 | Object value = values[i]; |
| 175 | values2[i] = cast(type, value, pojoContext); |
| 176 | if (isPrimative(type) && values2[i] == null) |
| 177 | return (T) throwCannotBeNull(type.getName(), fieldName); |
| 178 | } |
| 179 | try { |
| 180 | return cons.newInstance(values2); |
| 181 | } catch (IllegalArgumentException e) { |
| 182 | LOG.error(Thread.currentThread().getName() + ": Attempting to call " + cons + " with " + asList(values2), e); |
| 183 | throw e; |
| 184 | } catch (IllegalAccessException e) { |
| 185 | InternalError error = new InternalError(type.getName() + ": Unable to use the constructor " + cons); |
| 186 | error.initCause(e); |
| 187 | throw error; |
| 188 | } catch (InvocationTargetException e) { |
| 189 | return (T) throwCannotBeNull(type.getName(), fields[getArgumentNumber(type, e)]); |
| 190 | } |
| 191 | } |
| 192 | T obj; |
| 193 | try { |
| 194 | obj = type.newInstance(); |
| 195 | } catch (IllegalAccessException e) { |
| 196 | IllegalArgumentException e2 = new IllegalArgumentException(e.toString()); |
| 197 | e2.initCause(e.getCause()); |
| 198 | throw e2; |
| 199 | } |
| 200 | try { |
| 201 | int manditoryFieldCount = 0; |
| 202 | for (int i = 0; i < fields.length; i++) { |
| 203 | String key = fields[i]; |
| 204 | Object value = values[i]; |
| 205 | |
| 206 | Field f = fieldMap.get(key); |
| 207 | if (f == null) |
| 208 | throw new IllegalArgumentException(type + ": Unable to find field " + key); |
| 209 | if (value != null && mandatoryFields.contains(key)) |
| 210 | manditoryFieldCount++; |
| 211 | f.set(obj, value); |
| 212 | } |
| 213 | if (manditoryFieldCount < mandatoryFields.size()) { |
| 214 | Set<String> mFields = new LinkedHashSet<String>(mandatoryFields); |
| 215 | for (String key : fieldOrder.keySet()) { |
| 216 | int idx = fieldOrder.get(key); |
| 217 | Object value = values[idx]; |
| 218 | if (value != null) |
| 219 | mFields.remove(key); |
| 220 | } |
| 221 | throw new IllegalArgumentException(type + ": Manditory fieldMap not set=" + mFields + ", map=" + new MapArray<String, Object>(fieldOrder, values)); |
| 222 | } |
| 223 | } catch (IllegalAccessException e) { |
| 224 | InternalError error = new InternalError(type + ": Unable to set fieldMap."); |
| 225 | error.initCause(e); |
| 226 | throw error; |
| 227 | } |
| 228 | return obj; |
| 229 | } |
| 230 | |
| 231 | static int getArgumentNumber(Class<?> type, InvocationTargetException e) { |
| 232 | Throwable cause = e.getCause(); |
| 233 | if (cause instanceof IllegalArgumentException) { |
| 234 | String mesg = cause.getMessage(); |
| 235 | if (mesg.startsWith("Argument ") && mesg.endsWith(" must not be null")) { |
| 236 | String[] words = mesg.split(" "); |
| 237 | try { |
| 238 | return Integer.parseInt(words[1]); |
| 239 | } catch (NumberFormatException ignored) { |
| 240 | throw (IllegalArgumentException) cause; |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | throw new IllegalStateException(type.getName() + ": Failed to build.", e.getCause()); |
| 245 | } |
| 246 | |
| 247 | @NotNull |
| 248 | public static <T> T build(@NotNull Class<T> class2, @NotNull Map<String, Object> map, @NotNull PojoContext pojoContext) throws InstantiationException { |
| 249 | return acquire(class2).build(map, pojoContext); |
| 250 | } |
| 251 | |
| 252 | @NotNull |
| 253 | public static <T> T build(@NotNull Class<T> class2, @NotNull Object[] values, @NotNull PojoContext pojoContext) throws InstantiationException { |
| 254 | return acquire(class2).build(values, pojoContext); |
| 255 | } |
| 256 | |
| 257 | @NotNull |
| 258 | public static <T> T[] buildArray(@NotNull List<String[]> strings, Class<T> clazz, @NotNull PojoContext pojoContext) throws InstantiationException { |
| 259 | List<T> ret = new ArrayList<T>(); |
| 260 | if (strings.size() > 1) { |
| 261 | String[] heading = strings.get(0); |
| 262 | Map<String, Integer> keys = MapArray.asKeys(heading); |
| 263 | DataValueClass<T> ph = acquire(clazz); |
| 264 | for (String[] row : strings.subList(1, strings.size())) { |
| 265 | ret.add(ph.build(new MapArray(keys, row), pojoContext)); |
| 266 | } |
| 267 | } |
| 268 | return ret.toArray((T[]) Array.newInstance(clazz, ret.size())); |
| 269 | } |
| 270 | |
| 271 | @Nullable |
| 272 | public static <T> T cast(@NotNull Class<? extends T> clazz, @Nullable Object obj) throws UnknownFormatConversionException { |
| 273 | return cast(clazz, obj, PojoContext.EMPTY); |
| 274 | } |
| 275 | |
| 276 | @SuppressWarnings({"MethodWithMultipleReturnPoints", "OverlyComplexMethod", "OverlyCoupledMethod", "OverlyLongMethod"}) |
| 277 | @Nullable |
| 278 | public static <T> T cast(@NotNull Class<? extends T> clazz, @Nullable Object obj, @NotNull PojoContext pojoContext) throws UnknownFormatConversionException { |
| 279 | if (obj == null || "null".equals(obj)) return returnNull(clazz); |
| 280 | if (clazz == Object.class) return (T) obj; |
| 281 | // occurs very often. |
| 282 | if (clazz == Map.class && obj instanceof Map) return (T) obj; |
| 283 | |
| 284 | // if a primative update to the object type. |
| 285 | Class clazz2 = PRIMATIVE_MATCH.get(clazz); |
| 286 | if (clazz2 == null) clazz2 = clazz; |
| 287 | |
| 288 | // check the object's class. |
| 289 | Class objClass = obj.getClass(); |
| 290 | if (objClass == String.class) obj = expand((String) obj, pojoContext.properties); |
| 291 | if (clazz2 == objClass) return (T) obj; |
| 292 | |
| 293 | if (obj instanceof Number) { |
| 294 | if (clazz2 == Number.class) //noinspection CastConflictsWithInstanceof |
| 295 | return (T) obj; |
| 296 | if (clazz2 == Long.class) return (T) Long.valueOf(((Number) obj).longValue()); |
| 297 | if (clazz2 == Integer.class) return (T) Integer.valueOf(((Number) obj).intValue()); |
| 298 | if (clazz2 == Double.class) return (T) Double.valueOf(((Number) obj).doubleValue()); |
| 299 | if (clazz2 == Float.class) return (T) Float.valueOf(((Number) obj).floatValue()); |
| 300 | if (clazz2 == Short.class) return (T) Short.valueOf(((Number) obj).shortValue()); |
| 301 | if (clazz2 == Byte.class) return (T) Byte.valueOf(((Number) obj).byteValue()); |
| 302 | } |
| 303 | if (objClass != String.class) { |
| 304 | if (LOG.isDebugEnabled()) |
| 305 | LOG.debug(Thread.currentThread().getName() + ": Casting " + obj + " from " + obj.getClass() + " to " + clazz2); |
| 306 | if (isAssignableFrom(clazz2, objClass)) return (T) obj; |
| 307 | } |
| 308 | String asStr = obj.toString(); |
| 309 | if (clazz2 == String.class) return (T) asStr; |
| 310 | if (clazz2 == String[].class) return (T) (Object) parseArray(asStr); |
| 311 | if (clazz2 == List.class) return (T) asList(parseArray(asStr)); |
| 312 | if (clazz2 == Set.class) return (T) new LinkedHashSet<String>(asList(parseArray(asStr))); |
| 313 | if (clazz2 == Map.class) return (T) parseMap(asStr); |
| 314 | if (clazz2 == Named.Source.class) return (T) new Named.MapSource<String>(parseMap(asStr)); |
| 315 | |
| 316 | // empty string means null for all other types. |
| 317 | if ("".equals(obj)) return returnNull(clazz); |
| 318 | if (clazz2 == Long.class) return (T) Long.valueOf(Long.parseLong(asStr)); |
| 319 | if (clazz2 == Integer.class) return (T) Integer.valueOf(Integer.parseInt(asStr)); |
| 320 | if (clazz2 == Double.class) return (T) Double.valueOf(asStr); |
| 321 | if (clazz2 == Short.class) return (T) Short.valueOf(Short.parseShort(asStr)); |
| 322 | if (clazz2 == Byte.class) return (T) Byte.valueOf(Byte.parseByte(asStr)); |
| 323 | if (clazz2 == Float.class) return (T) Float.valueOf(asStr); |
| 324 | if (clazz2 == Boolean.class) return (T) StringUtils.parseBoolean(asStr); |
| 325 | if (clazz2 == BigInteger.class) return (T) new BigInteger(asStr); |
| 326 | if (clazz2 == BigDecimal.class || clazz2 == Number.class) return (T) new BigDecimal(asStr); |
| 327 | if (clazz2 == Character.class) return (T) Character.valueOf(asStr.charAt(0)); |
| 328 | if (isAssignableFrom(Date.class, clazz2)) return (T) parseDate(asStr); |
| 329 | if (isAssignableFrom(Enum.class, clazz2)) return (T) Enum.valueOf((Class<Enum>) clazz2, toUpperCase(asStr)); |
| 330 | if (clazz2 == Class.class || clazz2 == DataValueClass.class) return (T) asClass(clazz2, asStr, pojoContext); |
| 331 | Class<?> componentType = clazz2.getComponentType(); |
| 332 | if (componentType != null) return (T) asArray(componentType, asStr); |
| 333 | return createViaReflection(clazz, obj); |
| 334 | } |
| 335 | |
| 336 | private static <T> T createViaReflection(Class<T> type, Object object) { |
| 337 | // look for a static method valueOf(String) |
| 338 | try { |
| 339 | Method valueOfString = type.getMethod("valueOf", STRING_CLASS); |
| 340 | if ((valueOfString.getModifiers() & Modifier.STATIC) == Modifier.STATIC) { |
| 341 | valueOfString.setAccessible(true); |
| 342 | return (T) valueOfString.invoke(null, object.toString()); |
| 343 | } |
| 344 | } catch (NoSuchMethodException ignored) { |
| 345 | // ignored |
| 346 | } catch (IllegalAccessException e) { |
| 347 | return throwCannotParse(object, type, e); |
| 348 | } catch (InvocationTargetException e) { |
| 349 | throwCannotParse(object, type, e.getCause()); |
| 350 | } |
| 351 | |
| 352 | // look for constructor(String) |
| 353 | try { |
| 354 | Constructor consString = type.getDeclaredConstructor(STRING_CLASS); |
| 355 | consString.setAccessible(true); |
| 356 | return (T) consString.newInstance(object.toString()); |
| 357 | } catch (NoSuchMethodException ignored) { |
| 358 | // ignored |
| 359 | } catch (IllegalAccessException e) { |
| 360 | throwCannotParse(object, type, e); |
| 361 | } catch (InvocationTargetException e) { |
| 362 | throwCannotParse(object, type, e.getCause()); |
| 363 | } catch (InstantiationException e) { |
| 364 | throwCannotParse(object, type, e); |
| 365 | } |
| 366 | return throwCannotParse(object, type, null); |
| 367 | } |
| 368 | |
| 369 | private static <T> T throwCannotParse(Object object, Class<T> type, Throwable e) { |
| 370 | throw new IllegalArgumentException("Cannot parse " + object + " as " + type, e); |
| 371 | } |
| 372 | |
| 373 | private static <T> T asArray(Class<?> componentType, String asStr) { |
| 374 | String[] values = parseArray(asStr); |
| 375 | Object array = Array.newInstance(componentType, values.length); |
| 376 | for (int i = 0; i < values.length; i++) |
| 377 | Array.set(array, i, cast(componentType, values[i])); |
| 378 | return (T) array; |
| 379 | } |
| 380 | |
| 381 | private static Object asClass(Class clazz2, String asStr, PojoContext pojoContext) { |
| 382 | Class pClass = CLASS_LOOKUP.get(asStr); |
| 383 | if (pClass != NoClassFound.class && pClass != null) return pClass; |
| 384 | |
| 385 | // strip any "class " or "interface " prefix. |
| 386 | int pos = asStr.indexOf(' '); |
| 387 | if (pos >= 0) asStr = asStr.substring(pos + 1); |
| 388 | for (String packageName : pojoContext.getPackagePath()) { |
| 389 | for (String classSep : CLASS_SEP) { |
| 390 | String name = packageName + classSep + asStr; |
| 391 | Class<?> aClass = forName(name); |
| 392 | if (aClass != null) |
| 393 | return clazz2 == DataValueClass.class ? acquire(aClass) : aClass; |
| 394 | } |
| 395 | } |
| 396 | throw new IllegalArgumentException("Class " + asStr + " not found in " + asList(pojoContext.getPackagePath())); |
| 397 | } |
| 398 | |
| 399 | @Nullable @SuppressWarnings("SameReturnValue") |
| 400 | private static <T> T returnNull(Class<T> clazz) { |
| 401 | if (isPrimative(clazz)) throw new IllegalArgumentException("Cannot cast null to " + clazz); |
| 402 | return null; |
| 403 | } |
| 404 | |
| 405 | private static String[] parseArray(Object object) { |
| 406 | String text = object.toString(); |
| 407 | if (text.length() == 0 || "[]".equals(text)) return NO_STRINGS; |
| 408 | text = trimChars(text, '[', ']'); |
| 409 | return text.split(", ?"); |
| 410 | } |
| 411 | |
| 412 | private static String trimChars(String text, char start, char end) { |
| 413 | if (text.charAt(0) == start && text.charAt(text.length() - 1) == end) |
| 414 | text = text.substring(1, text.length() - 1); |
| 415 | return text; |
| 416 | } |
| 417 | |
| 418 | public static Map<String, String> parseMap(Object object) { |
| 419 | String text = object.toString(); |
| 420 | if (text.length() == 0 || "{}".equals(text)) return Collections.emptyMap(); |
| 421 | text = trimChars(text, '{', '}'); |
| 422 | Map<String, String> ret = new LinkedHashMap<String, String>(); |
| 423 | String[] keyValues = text.split(", ?"); |
| 424 | for (String keyValue : keyValues) { |
| 425 | String[] parts = keyValue.split("=", 2); |
| 426 | if (parts.length == 1) |
| 427 | ret.put(parts[0], ""); |
| 428 | else |
| 429 | ret.put(parts[0], parts[1]); |
| 430 | } |
| 431 | return ret; |
| 432 | } |
| 433 | |
| 434 | public static <T> boolean equals(@NotNull T obj1, @Nullable Object obj2) { |
| 435 | //noinspection ObjectEquality |
| 436 | if (obj1 == obj2) return true; |
| 437 | if (obj2 == null) return false; |
| 438 | if (obj2.getClass() != obj1.getClass()) return false; |
| 439 | |
| 440 | DataValueClass<T> dvc = acquire((Class<T>) obj1.getClass()); |
| 441 | try { |
| 442 | for (Field f : dvc.fieldMap.values()) { |
| 443 | Object val1 = f.get(obj1); |
| 444 | Object val2 = f.get(obj2); |
| 445 | if (val1 != val2 && (val1 == null || !val1.equals(val2))) |
| 446 | return false; |
| 447 | } |
| 448 | } catch (IllegalAccessException e) { |
| 449 | throw new AssertionError(e); |
| 450 | } |
| 451 | return true; |
| 452 | } |
| 453 | |
| 454 | public static boolean equalsCheckNull(@Nullable Object obj1, @Nullable Object obj2) { |
| 455 | //noinspection ObjectEquality |
| 456 | if (obj1 == obj2) return true; |
| 457 | //noinspection SimplifiableIfStatement |
| 458 | if (obj1 == null || obj2 == null) return false; |
| 459 | return obj1.equals(obj2); |
| 460 | } |
| 461 | |
| 462 | static Object throwCannotBeNull(String objectName, String name) { |
| 463 | throw new IllegalArgumentException(objectName + ": Failed to build as field " + name + " cannot be null."); |
| 464 | } |
| 465 | |
| 466 | static Object throwUnmatched(String objectName, List<String> matched, Set<String> unmatched, Exception e) { |
| 467 | throw new IllegalStateException(objectName + ": Matched " + matched + " but was unable to match " + unmatched, e); |
| 468 | } |
| 469 | |
| 470 | @SuppressWarnings({"MarkerInterface"}) interface NoClassFound { |
| 471 | } |
| 472 | |
| 473 | @Nullable private static Class forName(@NotNull String name) { |
| 474 | Class clazz = CLASS_LOOKUP.get(name); |
| 475 | if (clazz != null) |
| 476 | return clazz == NoClassFound.class ? null : clazz; |
| 477 | Class clazz2; |
| 478 | try { |
| 479 | clazz2 = Class.forName(name); |
| 480 | } catch (ClassNotFoundException ignored) { |
| 481 | clazz2 = NoClassFound.class; |
| 482 | } |
| 483 | CLASS_LOOKUP.put(name, clazz2); |
| 484 | return clazz2 == NoClassFound.class ? null : clazz2; |
| 485 | } |
| 486 | |
| 487 | @NotNull public String[] getFields() { |
| 488 | return fields; |
| 489 | } |
| 490 | |
| 491 | @Nullable public <K> DataValueClass<K> getInferedKey(boolean firstField) { |
| 492 | if (fieldMap == null) |
| 493 | throw new IllegalStateException("FieldMap is null, cannot get key for " + type); |
| 494 | String name = type.getName(); |
| 495 | if (name.endsWith("$Key") || isPrimative(type)) |
| 496 | throw new IllegalStateException("Cannot get a key type for " + name); |
| 497 | Class clazz = forName(name + "$Key"); |
| 498 | if (clazz == null) |
| 499 | clazz = forName(name + "Key"); |
| 500 | if (clazz != null) |
| 501 | return acquire((Class<K>) clazz); |
| 502 | if (firstField) { |
| 503 | // cannot determine the key type of an interface. |
| 504 | if (type.isInterface() || type == Object.class) |
| 505 | return null; |
| 506 | |
| 507 | String key = fields[0]; |
| 508 | Field field = fieldMap.get(key); |
| 509 | if (field == null) |
| 510 | throw new IllegalArgumentException("Key " + key + " is not an attribute of " + type); |
| 511 | return (DataValueClass<K>) acquire(field.getType(), key); |
| 512 | } else { |
| 513 | return (DataValueClass<K>) acquire(Object.class); |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | @NotNull public Class<T> getType() { |
| 518 | return type; |
| 519 | } |
| 520 | |
| 521 | /** |
| 522 | * This is similar to Class.isNotAssignableFrom except it also handled primatives. |
| 523 | */ |
| 524 | public boolean isNotAssignableFrom(@NotNull Class class2) { |
| 525 | return !isAssignableFrom(type, class2); |
| 526 | } |
| 527 | |
| 528 | private static final Map<Pair<Class, Class>, Boolean> IS_ASSIGNABLE_CACHE = new ConcurrentHashMap<Pair<Class, Class>, Boolean>(256, 0.5f, 16); |
| 529 | |
| 530 | public static boolean isAssignableFrom(@NotNull Class class1, @NotNull Class class2) { |
| 531 | if (class1 == class2 || class1 == Object.class) return true; |
| 532 | Pair<Class, Class> key = new Pair<Class, Class>(class1, class2); |
| 533 | Boolean ret = IS_ASSIGNABLE_CACHE.get(key); |
| 534 | if (ret != null) return ret; |
| 535 | Class pclass1 = PRIMATIVE_MATCH.get(class1); |
| 536 | if (pclass1 != null) |
| 537 | return pclass1 == class2; |
| 538 | Class pclass2 = PRIMATIVE_MATCH.get(class2); |
| 539 | if (pclass2 != null) |
| 540 | return pclass2 == class1; |
| 541 | IS_ASSIGNABLE_CACHE.put(key, ret = class1.isAssignableFrom(class2)); |
| 542 | return ret; |
| 543 | } |
| 544 | |
| 545 | public static boolean isBaseType(@NotNull Class type) { |
| 546 | return BASE_CLASS_SET.contains(type) || isAssignableFrom(Enum.class, type); |
| 547 | } |
| 548 | |
| 549 | @NotNull |
| 550 | public static <T> T setNew(@NotNull T pojo, @NotNull String field, @NotNull Object value, Object... others) throws IllegalArgumentException { |
| 551 | DataValueClass<T> dataValueClass = (DataValueClass<T>) acquire(pojo.getClass()); |
| 552 | return dataValueClass.setNew2(pojo, field, value, others); |
| 553 | } |
| 554 | |
| 555 | @NotNull |
| 556 | private T setNew2(@NotNull T dataValue, @NotNull String field, @NotNull Object value, Object... others) throws IllegalArgumentException { |
| 557 | Object[] asArray = asArray2(dataValue); |
| 558 | setField(asArray, field, value); |
| 559 | for (int i = 0; i < others.length - 1; i += 2) |
| 560 | setField(asArray, others[i].toString(), others[i + 1]); |
| 561 | try { |
| 562 | return build(asArray, PojoContext.EMPTY); |
| 563 | } catch (InstantiationException e) { |
| 564 | return (T) throwFailedToSet(field, value, e, others); |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | private static Object throwFailedToSet(String field, Object value, InstantiationException e, Object... others) { |
| 569 | StringBuilder sb = new StringBuilder(); |
| 570 | if (field != null) |
| 571 | sb.append(field).append('=').append(value); |
| 572 | for (int i = 0; i < others.length - 1; i += 2) |
| 573 | sb.append(", ").append(others[i]).append('=').append(others[i + 1]); |
| 574 | throw new IllegalArgumentException("Failed to set " + sb, e.getCause()); |
| 575 | } |
| 576 | |
| 577 | @NotNull |
| 578 | public static <T2, T> T project(@NotNull Class<T> clazz, @NotNull T2 pojo, Object... keyValues) throws IllegalArgumentException { |
| 579 | DataValueClass<T> dataValueClass = acquire(clazz); |
| 580 | return dataValueClass.project2(pojo, keyValues); |
| 581 | } |
| 582 | |
| 583 | @NotNull private <T2> T project2(@NotNull T2 dataValue, Object... keyValues) throws IllegalArgumentException { |
| 584 | Map<String, Object> map = asMap(dataValue); |
| 585 | for (int i = 0; i < keyValues.length - 1; i += 2) |
| 586 | map.put(keyValues[i].toString(), keyValues[i + 1]); |
| 587 | try { |
| 588 | return build(map, PojoContext.EMPTY); |
| 589 | } catch (InstantiationException e) { |
| 590 | return (T) throwFailedToSet(null, null, e, keyValues); |
| 591 | } |
| 592 | } |
| 593 | |
| 594 | private void setField(Object[] asArray, String field, Object value) { |
| 595 | Integer fieldNum = fieldOrder.get(field); |
| 596 | if (fieldNum == null) throw new IllegalArgumentException(FIELD + field + " not a field of " + type); |
| 597 | if (fieldMap == null) throwCannotBeSet(field); |
| 598 | else { |
| 599 | Field f = fieldMap.get(field); |
| 600 | if (f == null) throwCannotBeSet(field); |
| 601 | else |
| 602 | asArray[fieldNum] = cast(f.getType(), value); |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | private void throwCannotBeSet(String field) { |
| 607 | throw new IllegalArgumentException(FIELD + field + " cannot be set for " + type); |
| 608 | } |
| 609 | |
| 610 | public static <T> int hashCode(@NotNull T t) { |
| 611 | DataValueClass<T> dvc = acquire((Class<T>) t.getClass()); |
| 612 | int ret = 0; |
| 613 | try { |
| 614 | for (Field f : dvc.fieldMap.values()) { |
| 615 | Object obj = f.get(t); |
| 616 | ret = ret * 37 + (obj == null ? 0 : obj.hashCode()); |
| 617 | } |
| 618 | } catch (IllegalAccessException e) { |
| 619 | throw new AssertionError(e); |
| 620 | } |
| 621 | return ret; |
| 622 | } |
| 623 | |
| 624 | public static <T> String toString(@NotNull T t) { |
| 625 | StringBuilder sb = new StringBuilder(32); |
| 626 | String name = t.getClass().getName(); |
| 627 | int pos = name.lastIndexOf('$'); |
| 628 | if (pos < 0) |
| 629 | pos = name.lastIndexOf('.'); |
| 630 | sb.append(name.substring(pos + 1)); |
| 631 | sb.append(' '); |
| 632 | sb.append(asMap(t)); |
| 633 | return sb.toString(); |
| 634 | } |
| 635 | |
| 636 | public String toString() { |
| 637 | return type.getName(); |
| 638 | } |
| 639 | |
| 640 | private DataValueClass(Class<T> clazz) { |
| 641 | type = clazz; |
| 642 | getterMap = getGetters(clazz); |
| 643 | fieldMap = getFields(clazz); |
| 644 | cons = getConstructor(clazz, fieldMap); |
| 645 | Set<String> fieldsSet = new LinkedHashSet<String>(); |
| 646 | fieldsSet.addAll(fieldMap.keySet()); // the first field must be the first field in the code for getInferedKey(). |
| 647 | fieldsSet.addAll(getterMap.keySet()); // the getter field order is unsafe so put them last. |
| 648 | fields = fieldsSet.toArray(new String[fieldsSet.size()]); |
| 649 | fieldOrder = MapArray.asKeys(fields); |
| 650 | mandatoryFields = getMandatoryField(fieldMap); |
| 651 | notSerializable = isNotSerializable(clazz); |
| 652 | } |
| 653 | |
| 654 | private DataValueClass(Class<T> clazz, String name) { |
| 655 | type = clazz; |
| 656 | getterMap = null; |
| 657 | fieldMap = null; |
| 658 | cons = null; |
| 659 | fields = new String[]{name}; |
| 660 | fieldOrder = MapArray.asKeys(fields); |
| 661 | mandatoryFields = new HashSet<String>(); |
| 662 | mandatoryFields.add(name); |
| 663 | notSerializable = isNotSerializable(clazz); |
| 664 | } |
| 665 | |
| 666 | @Nullable private static <T> Constructor<T> getConstructor(Class<T> clazz, Map<String, Field> fieldMap) { |
| 667 | Class[] classes = new Class[fieldMap.size()]; |
| 668 | int i = 0; |
| 669 | for (Field f : fieldMap.values()) |
| 670 | classes[i++] = f.getType(); |
| 671 | try { |
| 672 | Constructor<T> cons = clazz.getDeclaredConstructor(classes); |
| 673 | cons.setAccessible(true); |
| 674 | return cons; |
| 675 | } catch (NoSuchMethodException ignored) { |
| 676 | return null; |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | private static Set<String> getMandatoryField(Map<String, Field> fields) { |
| 681 | Set<String> ret = new LinkedHashSet<String>(); |
| 682 | for (Field f : fields.values()) { |
| 683 | Class<?> type = f.getType(); |
| 684 | if (isPrimative(type) || f.getAnnotation(NotNull.class) != null) |
| 685 | ret.add(f.getName()); |
| 686 | } |
| 687 | return ret; |
| 688 | } |
| 689 | |
| 690 | @NotNull static Map<String, Field> getFields(Class clazz) { |
| 691 | Map<String, Field> fields; |
| 692 | Class superclass = clazz.getSuperclass(); |
| 693 | if (isTopClass(superclass)) |
| 694 | fields = getFields(superclass); |
| 695 | else |
| 696 | fields = new LinkedHashMap<String, Field>(); |
| 697 | for (Field f : clazz.getDeclaredFields()) { |
| 698 | // drop static or transient. |
| 699 | if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) |
| 700 | continue; |
| 701 | f.setAccessible(true); |
| 702 | fields.put(f.getName(), f); |
| 703 | } |
| 704 | return fields; |
| 705 | } |
| 706 | |
| 707 | private static boolean isTopClass(Class superclass) { |
| 708 | return superclass != null && superclass != Object.class && superclass != DataValue.class; |
| 709 | } |
| 710 | |
| 711 | @NotNull private static Map<String, Method> getGetters(Class<?> clazz) { |
| 712 | Map<String, Method> getters; |
| 713 | Class superclass = clazz.getSuperclass(); |
| 714 | if (isTopClass(superclass)) |
| 715 | getters = getGetters(superclass); |
| 716 | else |
| 717 | getters = new LinkedHashMap<String, Method>(); |
| 718 | for (Method method : clazz.getDeclaredMethods()) { |
| 719 | if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC | Modifier.TRANSIENT)) != Modifier.PUBLIC) |
| 720 | continue; |
| 721 | if (isGetter(method)) { |
| 722 | method.setAccessible(true); |
| 723 | getters.put(getNameFromGetter(method.getName()), method); |
| 724 | } |
| 725 | } |
| 726 | return getters; |
| 727 | } |
| 728 | |
| 729 | private static boolean isGetter(Method method) { |
| 730 | Class<?> returnType = method.getReturnType(); |
| 731 | if (returnType == void.class) |
| 732 | return false; |
| 733 | Class<?>[] classes = method.getParameterTypes(); |
| 734 | if (classes.length > 0) |
| 735 | return false; |
| 736 | String name = method.getName(); |
| 737 | if (name.startsWith("get")) |
| 738 | return name.length() > 3; |
| 739 | // otherwise must be a boolean getter. |
| 740 | if (returnType != Boolean.class && returnType != boolean.class) |
| 741 | return false; |
| 742 | if (name.startsWith("has")) |
| 743 | return name.length() > 3; |
| 744 | //noinspection SimplifiableIfStatement |
| 745 | if (name.startsWith("is")) |
| 746 | return name.length() > 2; |
| 747 | return false; |
| 748 | } |
| 749 | |
| 750 | private static String getNameFromGetter(String name) { |
| 751 | String name2; |
| 752 | if (name.startsWith("get") || name.startsWith("has")) { |
| 753 | name2 = name.substring(3); |
| 754 | } else if (name.startsWith("is")) { |
| 755 | name2 = name.substring(2); |
| 756 | } else { |
| 757 | return throwNotAGetter(name); |
| 758 | } |
| 759 | if (name2.length() < 1) |
| 760 | return throwNotAGetter(name); |
| 761 | if (Character.isUpperCase(name2.charAt(0))) { |
| 762 | // if two upper chars leave unchanged. |
| 763 | if (name2.length() > 1 && Character.isUpperCase(name2.charAt(1))) { |
| 764 | return name2; |
| 765 | } |
| 766 | return Character.toLowerCase(name2.charAt(0)) + name2.substring(1); |
| 767 | } |
| 768 | return name2; |
| 769 | } |
| 770 | |
| 771 | private static String throwNotAGetter(String name) { |
| 772 | throw new IllegalArgumentException("Not a getter method " + name); |
| 773 | } |
| 774 | |
| 775 | public static boolean isPrimative(Class clazz) { |
| 776 | return PRIMATIVE_MATCH.containsKey(clazz); |
| 777 | } |
| 778 | |
| 779 | public static Set<String> getFieldNames(Class clazz) { |
| 780 | DataValueClass dvClass = acquire(clazz); |
| 781 | return new LinkedHashSet(Arrays.asList(dvClass.getFields())); |
| 782 | } |
| 783 | |
| 784 | public static Object getField(@NotNull Object value, @NotNull String name) throws NoSuchElementException, IllegalAccessException, InvocationTargetException { |
| 785 | DataValueClass<?> dvClass = acquire(value.getClass()); |
| 786 | Method method = dvClass.getterMap.get(name); |
| 787 | if (method != null) |
| 788 | return method.invoke(value); |
| 789 | Field field = (Field) dvClass.fieldMap.get(name); |
| 790 | if (field == null) |
| 791 | throw new NoSuchElementException("Cannot find field " + name + " for " + value.getClass()); |
| 792 | try { |
| 793 | return field.get(value); |
| 794 | } catch (IllegalAccessException ignored) { |
| 795 | throw new NoSuchElementException("Cannot access field " + name + " for " + value.getClass()); |
| 796 | } |
| 797 | } |
| 798 | |
| 799 | static { |
| 800 | DatableUtils.registerBuilder(DataValueClass.class, new Mapping<DataInput, DataValueClass>() { |
| 801 | public DataValueClass convert(DataInput in) throws IOException { |
| 802 | Class type = (Class) DatableUtils.readObject(in); |
| 803 | return acquire(type); |
| 804 | } |
| 805 | }); |
| 806 | } |
| 807 | |
| 808 | public void writeData(@NotNull DataOutput out) throws IOException { |
| 809 | DatableUtils.writeObject(out, type); |
| 810 | } |
| 811 | |
| 812 | public boolean isNotSerializable() { |
| 813 | return notSerializable; |
| 814 | } |
| 815 | |
| 816 | private boolean isNotSerializable(Class<T> clazz) { |
| 817 | return !isAssignableFrom(Serializable.class, clazz) && !isAssignableFrom(Datable.class, clazz) && !isAssignableFrom(DataValue.class, clazz) && clazz.getComponentType() == null; |
| 818 | } |
| 819 | } |