/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.integration.regression;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.joda.beans.Bean;
import org.joda.beans.MetaProperty;
import com.google.common.collect.Maps;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.math.curve.Curve;
import com.opengamma.analytics.util.serialization.InvokedSerializedForm;
import com.opengamma.core.marketdatasnapshot.VolatilitySurfaceData;
import com.opengamma.financial.analytics.LabelledMatrix1D;
import com.opengamma.util.ClassMap;
import com.opengamma.util.fudgemsg.WriteReplaceHelper;
import com.opengamma.util.money.CurrencyAmount;
import com.opengamma.util.money.MultipleCurrencyAmount;
/**
* Checks whether values are close enough to equality to satisfy the regression test.
*/
public final class EqualityChecker {
// TODO static method to populate these from the outside
private static final Map<Class<?>, TypeHandler<?>> s_handlers = new ClassMap<>();
private static final Map<MetaProperty<?>, Comparator<?>> s_propertyComparators = Maps.newHashMap();
private static final ObjectArrayHandler s_objectArrayHandler = new ObjectArrayHandler();
static {
s_handlers.put(Double.class, new DoubleHandler());
s_handlers.put(double[].class, new PrimitiveDoubleArrayHandler());
s_handlers.put(Double[].class, new DoubleArrayHandler());
s_handlers.put(Object[].class, s_objectArrayHandler);
s_handlers.put(List.class, new ListHandler());
s_handlers.put(YieldCurve.class, new YieldCurveHandler());
s_handlers.put(LabelledMatrix1D.class, new LabelledMatrix1DHandler());
s_handlers.put(MultipleCurrencyAmount.class, new MultipleCurrencyAmountHandler());
s_handlers.put(Bean.class, new BeanHandler());
s_handlers.put(InvokedSerializedForm.class, new InvokedSerializedFormHandler());
s_handlers.put(VolatilitySurfaceData.class, new VolatilitySurfaceDataHandler());
s_handlers.put(Map.class, new MapHandler());
s_propertyComparators.put(Curve.meta().name(), new AlwaysEqualComparator());
}
private EqualityChecker() {
}
/**
* Checks whether two values are close enough to equality to satisfy the regression test.
* @param o1 A value
* @param o2 Another value
* @param delta The maximum allowable difference between two double values
* @return true If the values are close enough to be considered equal
*/
public static boolean equals(Object o1, Object o2, double delta) {
if (o1 == null && o2 == null) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (!o1.getClass().equals(o2.getClass())) {
return false;
}
// for normal classes this method returns the object itself. for instances of anonymous inner classes produced
// by a factory method it returns an instance of InvokedSerializedForm which encodes the factory method and
// arguments needed to recreate the object. comparing anonymous classes is obviously fraught with difficulties,
// but comparing the serialized form will work
Object value1 = WriteReplaceHelper.writeReplace(o1);
Object value2 = WriteReplaceHelper.writeReplace(o2);
@SuppressWarnings("unchecked")
TypeHandler<Object> handler = (TypeHandler<Object>) s_handlers.get(value1.getClass());
if (handler != null) {
return handler.equals(value1, value2, delta);
} else {
// ClassMap doesn't handle subtyping and arrays, this uses the Object[] handler for non-primitive arrays
if (value1.getClass().isArray() && Object[].class.isAssignableFrom(value1.getClass())) {
return s_objectArrayHandler.equals((Object[]) value1, (Object[]) value2, delta);
} else {
return Objects.equals(value1, value2);
}
}
}
/**
* Handles equality checking for a specific type.
* @param <T> The type
*/
public interface TypeHandler<T> {
/**
* Returns true if the values are close enough to equality to satisfy the regression test.
* @param value1 A value
* @param value2 Another value
* @param delta The maximum allowable difference between two double values
* @return true If the values are close enough to be considered equal
*/
boolean equals(T value1, T value2, double delta);
}
private static final class YieldCurveHandler implements TypeHandler<YieldCurve> {
@Override
public boolean equals(YieldCurve value1, YieldCurve value2, double delta) {
return EqualityChecker.equals(value1.getCurve(), value2.getCurve(), delta);
}
}
private static final class DoubleArrayHandler implements TypeHandler<Double[]> {
@Override
public boolean equals(Double[] value1, Double[] value2, double delta) {
if (value1.length != value2.length) {
return false;
}
for (int i = 0; i < value1.length; i++) {
double item1 = value1[i];
double item2 = value2[i];
if (Math.abs(item1 - item2) > delta) {
return false;
}
}
return true;
}
}
private static final class MultipleCurrencyAmountHandler implements TypeHandler<MultipleCurrencyAmount> {
@Override
public boolean equals(MultipleCurrencyAmount value1, MultipleCurrencyAmount value2, double delta) {
for (CurrencyAmount currencyAmount : value1) {
double amount1 = currencyAmount.getAmount();
double amount2;
try {
amount2 = value2.getAmount(currencyAmount.getCurrency());
} catch (IllegalArgumentException e) {
return false;
}
if (!EqualityChecker.equals(amount1, amount2, delta)) {
return false;
}
}
return true;
}
}
private static final class ObjectArrayHandler implements TypeHandler<Object[]> {
@Override
public boolean equals(Object[] value1, Object[] value2, double delta) {
if (value1.length != value2.length) {
return false;
}
for (int i = 0; i < value1.length; i++) {
Object item1 = value1[i];
Object item2 = value2[i];
if (!EqualityChecker.equals(item1, item2, delta)) {
return false;
}
}
return true;
}
}
private static final class PrimitiveDoubleArrayHandler implements TypeHandler<double[]> {
@Override
public boolean equals(double[] value1, double[] value2, double delta) {
if (value1.length != value2.length) {
return false;
}
for (int i = 0; i < value1.length; i++) {
double item1 = value1[i];
double item2 = value2[i];
if (Math.abs(item1 - item2) > delta) {
return false;
}
}
return true;
}
}
private static final class DoubleHandler implements TypeHandler<Double> {
@Override
public boolean equals(Double value1, Double value2, double delta) {
return Math.abs(value1 - value2) <= delta;
}
}
private static final class ListHandler implements TypeHandler<List<?>> {
@Override
public boolean equals(List<?> value1, List<?> value2, double delta) {
if (value1.size() != value2.size()) {
return false;
}
for (Iterator<?> it1 = value1.iterator(), it2 = value2.iterator(); it1.hasNext(); ) {
Object item1 = it1.next();
Object item2 = it2.next();
if (!EqualityChecker.equals(item1, item2, delta)) {
return false;
}
}
return true;
}
}
private static class BeanHandler implements TypeHandler<Bean> {
@Override
public boolean equals(Bean bean1, Bean bean2, double delta) {
for (MetaProperty<?> property : bean1.metaBean().metaPropertyIterable()) {
Object value1 = property.get(bean1);
Object value2 = property.get(bean2);
@SuppressWarnings("unchecked")
Comparator<Object> comparator = (Comparator<Object>) s_propertyComparators.get(property);
if (comparator == null) {
if (!EqualityChecker.equals(value1, value2, delta)) {
return false;
}
} else {
if (value1 == null && value2 == null) {
continue;
}
if (value1 == null || value2 == null) {
return false;
}
if (!value1.getClass().equals(value2.getClass())) {
return false;
}
if (comparator.compare(value1, value2) != 0) {
return false;
}
}
}
return true;
}
}
private static class InvokedSerializedFormHandler implements TypeHandler<InvokedSerializedForm> {
@Override
public boolean equals(InvokedSerializedForm value1, InvokedSerializedForm value2, double delta) {
if (!Objects.equals(value1.getOuterClass(), value2.getOuterClass())) {
return false;
}
if (!value1.getMethod().equals(value2.getMethod())) {
return false;
}
if (!EqualityChecker.equals(value1.getOuterInstance(), value2.getOuterInstance(), delta)) {
return false;
}
if (!EqualityChecker.equals(value1.getParameters(), value2.getParameters(), delta)) {
return false;
}
return true;
}
}
private static class VolatilitySurfaceDataHandler implements TypeHandler<VolatilitySurfaceData<?, ?>> {
@Override
public boolean equals(VolatilitySurfaceData<?, ?> value1, VolatilitySurfaceData<?, ?> value2, double delta) {
if (!Objects.equals(value1.getDefinitionName(), value2.getDefinitionName())) {
return false;
}
if (!Objects.equals(value1.getSpecificationName(), value2.getSpecificationName())) {
return false;
}
if (!Objects.equals(value1.getTarget(), value2.getTarget())) {
return false;
}
if (!Objects.equals(value1.getXLabel(), value2.getXLabel())) {
return false;
}
if (!Objects.equals(value1.getYLabel(), value2.getYLabel())) {
return false;
}
if (!EqualityChecker.equals(value1.asMap(), value2.asMap(), delta)) {
return false;
}
return true;
}
}
private static class MapHandler implements TypeHandler<Map<?, ?>> {
@Override
public boolean equals(Map<?, ?> map1, Map<?, ?> map2, double delta) {
if (!map1.keySet().equals(map2.keySet())) {
return false;
}
for (Map.Entry<?, ?> entry : map1.entrySet()) {
Object value1 = entry.getValue();
Object value2 = map2.get(entry.getKey());
if (!EqualityChecker.equals(value1, value2, delta)) {
return false;
}
}
return true;
}
}
private static class LabelledMatrix1DHandler implements TypeHandler<LabelledMatrix1D<?, ?>> {
@Override
public boolean equals(LabelledMatrix1D<?, ?> value1, LabelledMatrix1D<?, ?> value2, double delta) {
if (!Objects.equals(value1.getLabelsTitle(), value2.getLabelsTitle())) {
return false;
}
if (!Objects.equals(value1.getValuesTitle(), value2.getValuesTitle())) {
return false;
}
if (!EqualityChecker.equals(value1.getKeys(), value2.getKeys(), delta)) {
return false;
}
if (!EqualityChecker.equals(value1.getLabels(), value2.getLabels(), delta)) {
return false;
}
if (!EqualityChecker.equals(value1.getValues(), value2.getValues(), delta)) {
return false;
}
if (!EqualityChecker.equals(value1.getDefaultTolerance(), value2.getDefaultTolerance(), delta)) {
return false;
}
return true;
}
}
private static class AlwaysEqualComparator implements Comparator<Object> {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
}
}