package water.util; import water.*; import water.api.Schema; import water.api.SchemaServer; import water.api.schemas3.FrameV3; import water.api.schemas3.KeyV3; import water.exceptions.H2OIllegalArgumentException; import water.exceptions.H2ONotFoundArgumentException; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * POJO utilities which cover cases similar to but not the same as Apache Commons PojoUtils. */ public class PojoUtils { public enum FieldNaming { CONSISTENT { @Override String toDest(String origin) { return origin; } @Override String toOrigin(String dest) { return dest; } }, DEST_HAS_UNDERSCORES { @Override String toDest(String origin) { return "_" + origin; } @Override String toOrigin(String dest) { return dest.substring(1); } }, ORIGIN_HAS_UNDERSCORES { @Override String toDest(String origin) { return origin.substring(1); } @Override String toOrigin(String dest) { return "_" + dest; } }; /** * Return destination name based on origin name. * @param origin name of origin argument * @return return a name of destination argument. */ abstract String toDest(String origin); /** * Return name of origin parameter derived from name of origin parameter. * @param dest name of destination argument. * @return return a name of origin argument. */ abstract String toOrigin(String dest); } /** * Copy properties "of the same name" from one POJO to the other. If the fields are * named consistently (both sides have fields named "_foo" and/or "bar") this acts like * Apache Commons PojoUtils.copyProperties(). If one side has leading underscores and * the other does not then the names are conformed according to the field_naming * parameter. * * It is also able to map fields between external types like Schema to their corresponding * internal types. * * @param dest Destination POJO * @param origin Origin POJO * @param field_naming Are the fields named consistently, or does one side have underscores? */ public static void copyProperties(Object dest, Object origin, FieldNaming field_naming) { copyProperties(dest, origin, field_naming, null); } /** * Copy properties "of the same name" from one POJO to the other. If the fields are * named consistently (both sides have fields named "_foo" and/or "bar") this acts like * Apache Commons PojoUtils.copyProperties(). If one side has leading underscores and * the other does not then the names are conformed according to the field_naming * parameter. * * @param dest Destination POJO * @param origin Origin POJO * @param field_naming Are the fields named consistently, or does one side have underscores? * @param skip_fields Array of origin or destination field names to skip */ public static void copyProperties(Object dest, Object origin, FieldNaming field_naming, String[] skip_fields) { copyProperties(dest, origin, field_naming, skip_fields, null); } /** * Copy properties "of the same name" from one POJO to the other. If the fields are * named consistently (both sides have fields named "_foo" and/or "bar") this acts like * Apache Commons PojoUtils.copyProperties(). If one side has leading underscores and * the other does not then the names are conformed according to the field_naming * parameter. * * @param dest Destination POJO * @param origin Origin POJO * @param field_naming Are the fields named consistently, or does one side have underscores? * @param skip_fields Array of origin or destination field names to skip * @param only_fields Array of origin or destination field names to include; ones not in this list will be skipped */ public static void copyProperties(Object dest, Object origin, FieldNaming field_naming, String[] skip_fields, String[] only_fields) { if (null == dest || null == origin) return; Field[] dest_fields = Weaver.getWovenFields(dest .getClass()); Field[] orig_fields = Weaver.getWovenFields(origin.getClass()); for (Field orig_field : orig_fields) { String origin_name = orig_field.getName(); String dest_name = field_naming.toDest(origin_name); if (skip_fields != null && (ArrayUtils.contains(skip_fields, origin_name) || ArrayUtils.contains(skip_fields, dest_name))) continue; if (only_fields != null && ! (ArrayUtils.contains(only_fields, origin_name) || ArrayUtils.contains(only_fields, dest_name))) continue; try { Field dest_field = null; for( Field fd : dest_fields ) { if (fd.getName().equals(dest_name)) { dest_field = fd; break; } } if( dest_field != null ) { dest_field.setAccessible(true); orig_field.setAccessible(true); // Log.info("PojoUtils.copyProperties, origin field: " + orig_field + "; destination field: " + dest_field); if (null == orig_field.get(origin)) { // // Assigning null to dest. // dest_field.set(dest, null); } else if (dest_field.getType().isArray() && orig_field.getType().isArray() && (dest_field.getType().getComponentType() != orig_field.getType().getComponentType())) { // // Assigning an array to another array. // // You can't use reflection to set an int[] with an Integer[]. Argh. // TODO: other types of arrays. . . if (dest_field.getType().getComponentType() == double.class && orig_field.getType().getComponentType() == Double.class) { // // Assigning an Double[] to an double[] // double[] copy = (double[]) orig_field.get(origin); dest_field.set(dest, copy); } else if (dest_field.getType().getComponentType() == Double.class && orig_field.getType().getComponentType() == double.class) { // // Assigning an double[] to an Double[] // Double[] copy = (Double[]) orig_field.get(origin); dest_field.set(dest, copy); } else if (dest_field.getType().getComponentType() == int.class && orig_field.getType().getComponentType() == Integer.class) { // // Assigning an Integer[] to an int[] // int[] copy = (int[]) orig_field.get(origin); dest_field.set(dest, copy); } else if (dest_field.getType().getComponentType() == Integer.class && orig_field.getType().getComponentType() == int.class) { // // Assigning an int[] to an Integer[] // Integer[] copy = (Integer[]) orig_field.get(origin); dest_field.set(dest, copy); } else if (Schema.class.isAssignableFrom(dest_field.getType().getComponentType()) && (Schema.getImplClass((Class<?extends Schema>)dest_field.getType().getComponentType())).isAssignableFrom(orig_field.getType().getComponentType())) { // // Assigning an array of impl fields to an array of schema fields, e.g. a DeepLearningParameters[] into a DeepLearningParametersV2[] // Class dest_component_class = dest_field.getType().getComponentType(); // NOTE: there can be a race on the source array, so shallow copy it. // If it has shrunk the elements might have dangling references. Iced[] orig_array = (Iced[]) orig_field.get(origin); int length = orig_array.length; Iced[] orig_array_copy = Arrays.copyOf(orig_array, length); // Will null pad if it has shrunk since calling length Schema[] translation = (Schema[]) Array.newInstance(dest_component_class, length); int version = ((Schema)dest).getSchemaVersion(); // Look up the schema for each element of the array; if not found fall back to the schema for the base class. for (int i = 0; i < length; i++) { Iced impl = orig_array_copy[i]; if (null == impl) { translation[i++] = null; // also can happen if the array shrank between .length and the copy } else { Schema s = null; try { s = SchemaServer.schema(version, impl); } catch (H2ONotFoundArgumentException e) { s = ((Schema) dest_field.getType().getComponentType().newInstance()); } translation[i] = s.fillFromImpl(impl); } } dest_field.set(dest, translation); } else if (Schema.class.isAssignableFrom(orig_field.getType().getComponentType()) && Iced.class.isAssignableFrom(dest_field.getType().getComponentType())) { // // Assigning an array of schema fields to an array of impl fields, e.g. a DeepLearningParametersV2[] into a DeepLearningParameters[] // // We can't check against the actual impl class I, because we can't instantiate the schema base classes to get the impl class from an instance: // dest_field.getType().getComponentType().isAssignableFrom(((Schema)f.getType().getComponentType().newInstance()).getImplClass())) { Class dest_component_class = dest_field.getType().getComponentType(); Schema[] orig_array = (Schema[]) orig_field.get(origin); int length = orig_array.length; Schema[] orig_array_copy = Arrays.copyOf(orig_array, length); Iced[] translation = (Iced[]) Array.newInstance(dest_component_class, length); for (int i = 0; i < length; i++) { Schema s = orig_array_copy[i]; translation[i] = s == null ? null : s.createAndFillImpl(); } dest_field.set(dest, translation); } else { throw H2O.fail("Don't know how to cast an array of: " + orig_field.getType().getComponentType() + " to an array of: " + dest_field.getType().getComponentType()); } // end of array handling } else if (dest_field.getType() == Key.class && Keyed.class.isAssignableFrom(orig_field.getType())) { // // Assigning a Keyed (e.g., a Frame or Model) to a Key. // dest_field.set(dest, ((Keyed) orig_field.get(origin))._key); } else if (orig_field.getType() == Key.class && Keyed.class.isAssignableFrom(dest_field.getType())) { // // Assigning a Key (for e.g., a Frame or Model) to a Keyed (e.g., a Frame or Model). // Value v = DKV.get((Key) orig_field.get(origin)); dest_field.set(dest, (null == v ? null : v.get())); } else if (KeyV3.class.isAssignableFrom(dest_field.getType()) && Keyed.class.isAssignableFrom(orig_field.getType())) { // // Assigning a Keyed (e.g., a Frame or Model) to a KeyV1. // dest_field.set(dest, KeyV3.make(((Class<? extends KeyV3>) dest_field.getType()), ((Keyed) orig_field.get(origin))._key)); } else if (KeyV3.class.isAssignableFrom(orig_field.getType()) && Keyed.class.isAssignableFrom(dest_field.getType())) { // // Assigning a KeyV1 (for e.g., a Frame or Model) to a Keyed (e.g., a Frame or Model). // KeyV3 k = (KeyV3)orig_field.get(origin); Value v = DKV.get(Key.make(k.name)); dest_field.set(dest, (null == v ? null : v.get())); } else if (KeyV3.class.isAssignableFrom(dest_field.getType()) && Key.class.isAssignableFrom(orig_field.getType())) { // // Assigning a Key to a KeyV1. // dest_field.set(dest, KeyV3.make(((Class<? extends KeyV3>) dest_field.getType()), (Key) orig_field.get(origin))); } else if (KeyV3.class.isAssignableFrom(orig_field.getType()) && Key.class.isAssignableFrom(dest_field.getType())) { // // Assigning a KeyV1 to a Key. // KeyV3 k = (KeyV3)orig_field.get(origin); dest_field.set(dest, (null == k.name ? null : Key.make(k.name))); } else if (dest_field.getType() == Pattern.class && String.class.isAssignableFrom(orig_field.getType())) { // // Assigning a String to a Pattern. // dest_field.set(dest, Pattern.compile((String) orig_field.get(origin))); } else if (orig_field.getType() == Pattern.class && String.class.isAssignableFrom(dest_field.getType())) { // // We are assigning a Pattern to a String. // dest_field.set(dest, orig_field.get(origin).toString()); } else if (dest_field.getType() == FrameV3.ColSpecifierV3.class && String.class.isAssignableFrom(orig_field.getType())) { // // Assigning a String to a ColSpecifier. Note that we currently support only the colname, not a frame name too. // dest_field.set(dest, new FrameV3.ColSpecifierV3((String) orig_field.get(origin))); } else if (orig_field.getType() == FrameV3.ColSpecifierV3.class && String.class.isAssignableFrom(dest_field.getType())) { // // We are assigning a ColSpecifierV2 to a String. The column_name gets copied. // dest_field.set(dest, ((FrameV3.ColSpecifierV3)orig_field.get(origin)).column_name); } else if (Enum.class.isAssignableFrom(dest_field.getType()) && String.class.isAssignableFrom(orig_field.getType())) { // // Assigning a String into an enum field. // Class<Enum> dest_class = (Class<Enum>)dest_field.getType(); dest_field.set(dest, Enum.valueOf(dest_class, (String) orig_field.get(origin))); } else if (Enum.class.isAssignableFrom(orig_field.getType()) && String.class.isAssignableFrom(dest_field.getType())) { // // Assigning an enum field into a String. // Object o = orig_field.get(origin); dest_field.set(dest, (o == null ? null : o.toString())); } else if (Schema.class.isAssignableFrom(dest_field.getType()) && Schema.getImplClass((Class<? extends Schema>) dest_field.getType()).isAssignableFrom(orig_field.getType())) { // // Assigning an impl field into a schema field, e.g. a DeepLearningParameters into a DeepLearningParametersV2. // dest_field.set(dest, SchemaServer.schema(/* ((Schema)dest).getSchemaVersion() TODO: remove HACK!! */ 3, (Class<? extends Iced>)orig_field.get(origin).getClass()).fillFromImpl((Iced) orig_field.get(origin))); } else if (Schema.class.isAssignableFrom(orig_field.getType()) && Schema.getImplClass((Class<? extends Schema>)orig_field.getType()).isAssignableFrom(dest_field.getType())) { // // Assigning a schema field into an impl field, e.g. a DeepLearningParametersV2 into a DeepLearningParameters. // Schema s = ((Schema)orig_field.get(origin)); dest_field.set(dest, s.fillImpl(s.createImpl())); } else if ((Schema.class.isAssignableFrom(dest_field.getType()) && Key.class.isAssignableFrom(orig_field.getType()))) { // // Assigning an impl field fetched via a Key into a schema field, e.g. a DeepLearningParameters into a DeepLearningParametersV2. // Note that unlike the cases above we don't know the type of the impl class until we fetch in the body of the if. // Key origin_key = (Key) orig_field.get(origin); Value v = DKV.get(origin_key); if (null == v || null == v.get()) { dest_field.set(dest, null); } else { if (((Schema)dest_field.get(dest)).getImplClass().isAssignableFrom(v.get().getClass())) { Schema s = ((Schema)dest_field.get(dest)); dest_field.set(dest, SchemaServer.schema(s.getSchemaVersion(), s.getImplClass()).fillFromImpl(v.get())); } else { Log.err("Can't fill Schema of type: " + dest_field.getType() + " with value of type: " + v.getClass() + " fetched from Key: " + origin_key); dest_field.set(dest, null); } } } else if (Schema.class.isAssignableFrom(orig_field.getType()) && Keyed.class.isAssignableFrom(dest_field.getType())) { // // Assigning a schema field into a Key field, e.g. a DeepLearningV2 into a (DeepLearningParameters) key. // Schema s = ((Schema)orig_field.get(origin)); dest_field.set(dest, ((Keyed)s.fillImpl(s.createImpl()))._key); } else { // // Normal case: not doing any type conversion. // dest_field.set(dest, orig_field.get(origin)); } } } catch (IllegalAccessException e) { Log.err("Illegal access exception trying to copy field: " + origin_name + " of class: " + origin.getClass() + " to field: " + dest_name + " of class: " + dest.getClass()); } catch (InstantiationException e) { Log.err("Instantiation exception trying to copy field: " + origin_name + " of class: " + origin.getClass() + " to field: " + dest_name + " of class: " + dest.getClass()); } catch (Exception e) { Log.err(e.getClass().getCanonicalName() + " Exception: " + origin_name + " of class: " + origin.getClass() + " to field: " + dest_name + " of class: " + dest.getClass()); throw e; } } } /** * Null out fields in this schema and its children as specified by parameters __exclude_fields and __include_fields. * <b>NOTE: modifies the scheme tree in place.</b> */ public static void filterFields(Object o, String includes, String excludes) { if (null == o) return; if (null == excludes || "".equals(excludes)) return; if (null != includes) // not yet implemented throw new H2OIllegalArgumentException("_include_fields", "filterFields", includes); String[] exclude_paths = excludes.split(","); for (String path : exclude_paths) { // for each path. . . int slash = path.indexOf("/"); if (-1 == slash || slash == path.length()) { // NOTE: handles trailing slash // we've hit the end: null the field, if it exists Field f = ReflectionUtils.findNamedField(o, path); if (null == f) throw new H2OIllegalArgumentException("_exclude_fields", "filterFields", path); try { f.set(o, null); } catch (IllegalAccessException e) { throw new H2OIllegalArgumentException("_exclude_fields", "filterFields", path); } } // hit the end of the path else { String first = path.substring(0, slash); String rest = path.substring(slash + 1); Field f = ReflectionUtils.findNamedField(o, first); if (null == f) throw new H2OIllegalArgumentException("_exclude_fields", "filterFields", path); if (f.getType().isArray() && Object.class.isAssignableFrom(f.getType().getComponentType())) { // recurse into the children with the "rest" of the path try { Object[] field_value = (Object[]) f.get(o); for (Object child : field_value) { filterFields(child, null, rest); } } catch (IllegalAccessException e) { throw new H2OIllegalArgumentException("_exclude_fields", "filterFields", path); } } else if (Object.class.isAssignableFrom(f.getType())) { // recurse into the child with the "rest" of the path try { Object field_value = f.get(o); filterFields(field_value, null, rest); } catch (IllegalAccessException e) { throw new H2OIllegalArgumentException("_exclude_fields", "filterFields", path); } } else { throw new H2OIllegalArgumentException("_exclude_fields", "filterFields", path); } } // need to recurse } // foreach exclude_paths } public static boolean equals(Object a, Field fa, Object b, Field fb) { try { Object va = fa.get(a); Object vb = fb.get(b); return va == null ? vb == null : va.equals(vb); } catch (IllegalAccessException e) { e.printStackTrace(); return false; } } public static void setField(Object o, String fieldName, Object value, FieldNaming objectNamingConvention) { String destFieldName = null; switch (objectNamingConvention) { case CONSISTENT: destFieldName = fieldName; break; case DEST_HAS_UNDERSCORES: if (fieldName.startsWith("_")) destFieldName = fieldName; else destFieldName = "_" + fieldName; break; case ORIGIN_HAS_UNDERSCORES: if (fieldName.startsWith("_")) destFieldName = fieldName.substring(1); else throw new IllegalArgumentException("Wrong combination of options!"); break; } setField(o, destFieldName, value); } /** * Set given field to given value on given object. * * @param o object to modify * @param fieldName name of field to set * @param value value to write the the field */ public static void setField(Object o, String fieldName, Object value) { try { Field f = PojoUtils.getFieldEvenInherited(o, fieldName); f.setAccessible(true); if (null == value) { f.set(o, null); return; } if (List.class.isAssignableFrom(value.getClass()) && f.getType().isArray() && f.getType().getComponentType() == String.class) { // convert ArrayList to array and try again setField(o, fieldName, ((List)value).toArray(new String[0])); return; } // If it doesn't know any better, Gson deserializes all numeric types as doubles. // If our target is an integer type, cast. if (f.getType().isPrimitive() && f.getType() != value.getClass()) { // Log.debug("type conversion"); if (f.getType() == int.class && (value.getClass() == Double.class || value.getClass() == Float.class)) f.set(o, ((Double) value).intValue()); else if (f.getType() == long.class && (value.getClass() == Double.class || value.getClass() == Float.class)) f.set(o, ((Double) value).longValue()); else if (f.getType() == int.class && value.getClass() == Integer.class) f.set(o, ((Integer) value).intValue()); else if (f.getType() == long.class && (value.getClass() == Long.class || value.getClass() == Integer.class)) f.set(o, ((Long) value).longValue()); else { // Double -> double, Integer -> int will work: f.set(o, value); } } else if (f.getType().isArray() && value.getClass().isArray()) { if (f.getType().getComponentType() == value.getClass().getComponentType()) { // array of the same type on both sides f.set(o, value); } else if (f.getType().getComponentType() == int.class && value.getClass().getComponentType() == Integer.class) { Integer[] valuesTyped = ((Integer[])value); int[] valuesCast = new int[valuesTyped.length]; for (int i = 0; i < valuesTyped.length; i++) valuesCast[i] = valuesTyped[i]; f.set(o, valuesCast); } else if (f.getType().getComponentType() == long.class && value.getClass().getComponentType() == Long.class) { Long[] valuesTyped = ((Long[])value); long[] valuesCast = new long[valuesTyped.length]; for (int i = 0; i < valuesTyped.length; i++) valuesCast[i] = valuesTyped[i]; f.set(o, valuesCast); } else if (f.getType().getComponentType() == double.class && (value.getClass().getComponentType() == Float.class || value.getClass().getComponentType() == Double.class || value.getClass().getComponentType() == Integer.class || value.getClass().getComponentType() == Long.class)) { Double[] valuesTyped = ((Double[])value); double[] valuesCast = new double[valuesTyped.length]; for (int i = 0; i < valuesTyped.length; i++) valuesCast[i] = valuesTyped[i]; f.set(o, valuesCast); } else if (f.getType().getComponentType() == float.class && (value.getClass().getComponentType() == Float.class || value.getClass().getComponentType() == Double.class || value.getClass().getComponentType() == Integer.class || value.getClass().getComponentType() == Long.class)) { Float[] valuesTyped = ((Float[])value); float[] valuesCast = new float[valuesTyped.length]; for (int i = 0; i < valuesTyped.length; i++) valuesCast[i] = valuesTyped[i]; f.set(o, valuesCast); } else { throw new IllegalArgumentException("setField can't yet convert an array of: " + value.getClass().getComponentType() + " to an array of: " + f.getType().getComponentType()); } } else if (! f.getType().isPrimitive() && ! f.getType().isAssignableFrom(value.getClass())) { // TODO: pull the auto-type-conversion stuff out of copyProperties so we don't have limited copy-paste code here throw new IllegalArgumentException("setField can't yet convert a: " + value.getClass() + " to a: " + f.getType()); } else { // not casting a primitive type f.set(o, value); } } catch (NoSuchFieldException e) { throw new IllegalArgumentException("Field " + fieldName + " not found!", e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Field=" + fieldName + " cannot be set to value=" + value, e); } } /** * Gets a public, protected or private Field of an object, even if it's inherited. Neither Class.getField nor * Class.getDeclaredField do this. NOTE: the caller must call f.setAccessible(true) if they want to make private * fields accessible. */ public static Field getFieldEvenInherited(Object o, String name) throws NoSuchFieldException, SecurityException { Class clazz = o.getClass(); while (clazz != Object.class) { try { return clazz.getDeclaredField(name); } catch (Exception e) { // ignore } clazz = clazz.getSuperclass(); } throw new NoSuchFieldException("Failed to find field: " + name + " in object: " + o); } /** * Returns field value. * * @param o object to read field value from * @param name name of field to read * @return returns field value * * @throws java.lang.IllegalArgumentException when o is <code>null</code>, or field is not found, * or field cannot be read. */ public static Object getFieldValue(Object o, String name, FieldNaming fieldNaming) { if (o == null) throw new IllegalArgumentException("Cannot get the field from null object!"); String destName = fieldNaming.toDest(name); try { Field f = PojoUtils.getFieldEvenInherited(o, destName); // failing with fields declared in superclasses return f.get(o); } catch (NoSuchFieldException e) { throw new IllegalArgumentException("Field not found: '" + name + "/" + destName + "' on object " + o); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot get value of the field: '" + name + "/" + destName + "' on object " + o); } } /** * Take a object which potentially has default values for some fields and set * only those fields which are in the supplied JSON string. NOTE: Doesn't handle array fields yet. */ public static Object fillFromJson(Object o, String json) { Map<String, Object> setFields = (new com.google.gson.Gson()).fromJson(json, HashMap.class); return fillFromMap(o, setFields); } /** * Fill the fields of an Object from the corresponding fields in a Map. * @see #fillFromJson(Object, String) */ private static Object fillFromMap(Object o, Map<String, Object> setFields) { for (String key : setFields.keySet()) { // TODO: doesn't handle arrays yet! Object value = setFields.get(key); if (value instanceof Map) { // handle nested objects try { Field f = PojoUtils.getFieldEvenInherited(o, key); f.setAccessible(true); // In some cases, the target object has children already (e.g., defaults), while in other cases it doesn't. if (null == f.get(o)) f.set(o, f.getType().newInstance()); fillFromMap(f.get(o), (Map<String, Object>) value); } catch (NoSuchFieldException e) { throw new IllegalArgumentException("Field not found: '" + key + "' on object " + o); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot get value of the field: '" + key + "' on object " + o); } catch (InstantiationException e) { try { throw new IllegalArgumentException("Cannot create new child object of type: " + PojoUtils.getFieldEvenInherited(o, key).getClass().getCanonicalName() + " for field: '" + key + "' on object " + o); } catch (NoSuchFieldException ee) { // Can't happen: we've already checked for this. throw new IllegalArgumentException("Cannot create new child object of type for field: '" + key + "' on object " + o); } } } else { // Scalar or String, possibly with an automagic type conversion as copyProperties does. // TODO: refactor the type conversions out of copyProperties so they all work, and remove // this now-redundant code: try { Field f = PojoUtils.getFieldEvenInherited(o, key); f.setAccessible(true); if (f.getType().isAssignableFrom(FrameV3.ColSpecifierV3.class)) { setField(o, key, new FrameV3.ColSpecifierV3((String) value)); } else if (KeyV3.class.isAssignableFrom(f.getType())) { setField(o, key, KeyV3.make((Class<? extends KeyV3>)f.getType(), Key.make((String) value))); } else { setField(o, key, value); } } catch (NoSuchFieldException e) { throw new IllegalArgumentException("Field not found: '" + key + "' on object " + o); } } // else not a nested object } // for all fields in the map return o; } /** * Helper for Arrays.equals(). */ public static boolean arraysEquals(Object a, Object b) { if (a == null || ! a.getClass().isArray()) throw new H2OIllegalArgumentException("a", "arraysEquals", a); if (b == null || ! b.getClass().isArray()) throw new H2OIllegalArgumentException("b", "arraysEquals", b); if (a.getClass().getComponentType() != b.getClass().getComponentType()) throw new H2OIllegalArgumentException("Can't compare arrays of different types: " + a.getClass().getComponentType() + " and: " + b.getClass().getComponentType()); if (a.getClass().getComponentType() == boolean.class) return Arrays.equals((boolean[])a, (boolean[])b); if (a.getClass().getComponentType() == Boolean.class) return Arrays.equals((Boolean[])a, (Boolean[])b); if (a.getClass().getComponentType() == char.class) return Arrays.equals((char[])a, (char[])b); if (a.getClass().getComponentType() == short.class) return Arrays.equals((short[])a, (short[])b); if (a.getClass().getComponentType() == Short.class) return Arrays.equals((Short[])a, (Short[])b); if (a.getClass().getComponentType() == int.class) return Arrays.equals((int[])a, (int[])b); if (a.getClass().getComponentType() == Integer.class) return Arrays.equals((Integer[])a, (Integer[])b); if (a.getClass().getComponentType() == float.class) return Arrays.equals((float[])a, (float[])b); if (a.getClass().getComponentType() == Float.class) return Arrays.equals((Float[])a, (Float[])b); if (a.getClass().getComponentType() == double.class) return Arrays.equals((double[])a, (double[])b); if (a.getClass().getComponentType() == Double.class) return Arrays.equals((Double[])a, (Double[])b); return Arrays.deepEquals((Object[])a, (Object[])b); } /** * Same as Objects.equals(a, b) -- copied here because Objects class does not exist in Java6 (if we ever drop * support for Java6, this method can be removed). */ public static boolean equals(Object a, Object b) { return (a == b) || (a != null && b != null && b.equals(a)); } }