/*
* Copyright 2013, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.testing.utils.cougar.assertions;
import com.betfair.testing.utils.cougar.misc.AggregatedStepExpectedOutputMetaData;
import com.betfair.testing.utils.cougar.misc.DataTypeEnum;
import com.betfair.testing.utils.cougar.misc.ObjectUtil;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Asserts {@link java.util.Set} objects, actual and expected elements are compared according to their respective get*() values
* this implementation uses {@link com.betfair.assertions.general.assertion.ArrayListAssertion} in the {@link #preProcess(Object, com.betfair.assertions.core.beans.AggregatedStepExpectedOutputMetaData)} }
*
* @see com.betfair.assertions.general.assertion.ArrayListAssertion
*/
public class SetAssertion implements IAssertion {
private ArrayListAssertion arrayListAssertion = new ArrayListAssertion();
/**
* @param actualObject 4
* @param expectedMetaData including stepmetadata list in which namevalue pairs exist.
* @return Set object containing elements derived from expectedObjectMetaData
* @throws com.betfair.jett.exceptions.JETTFailFastException
* @see com.betfair.assertions.general.assertion.ArrayListAssertion#preProcess(Object, com.betfair.assertions.core.beans.AggregatedStepExpectedOutputMetaData)
* @see com.betfair.assertions.general.assertion.ArrayListAssertion#preProcess(Object, com.betfair.assertions.core.beans.AggregatedStepExpectedOutputMetaData)
*/
@Override
public Set<Object> preProcess(Object actualObject, AggregatedStepExpectedOutputMetaData expectedMetaData)
throws AssertionError {
// check metadata is empty
if (null == expectedMetaData || expectedMetaData.size() == 0 || expectedMetaData.getData().size() == 0
|| expectedMetaData.getData().get(0).size() == 0) {
return new LinkedHashSet();
}
ArrayList listActual = new ArrayList();
if (actualObject != null) {
Set set = (Set) actualObject;
listActual.addAll(set);
}
Set resultSet = new LinkedHashSet ();
List preProcess =this.arrayListAssertion.preProcess(listActual, expectedMetaData);
resultSet.addAll(preProcess);
return resultSet;
}
/**
* executes default null, size equivalency, boundrary checks
*
* @param expectedObject
* @param actualObject
* @return false if conditions are not met, otherwise true
* @throws com.betfair.assertions.asserter.exceptions.AssertionUtilityException
*/
private boolean doNullAndSizeCheck(Object expectedObject, Object actualObject)
throws AssertionError {
if (actualObject == null && expectedObject == null) {
AssertionUtils.jettAssertTrue("Expected object is <Null>" +
", checking if Actual Object is <null>. Actual object " + actualObject, true);
return false;
}
Set setActual = (Set) actualObject;
if (expectedObject == null && actualObject != null) {
AssertionUtils.jettAssertFalse("Expected object is <Null>" +
", checking if Actual Object is <null>. Actual object " + actualObject, true);
return false;
}
//expectedObject is implicity an arraylist
Set listExpected = (Set) expectedObject;
if (actualObject == null && (expectedObject != null && listExpected.size() > 0)) {
AssertionUtils.jettAssertFalse("Expected object is " + expectedObject + " " +
", checking if Actual Object is <null>. Actual object " + actualObject, true);
return false;
}
if ((actualObject != null && setActual.size() > 0) && expectedObject == null) {
AssertionUtils.jettAssertFalse("Actual object is " + actualObject + " " +
", checking if Expected Object is <null>. Expected object " + expectedObject, true);
return false;
}
if ((actualObject == null || setActual.size() == 0) && (expectedObject == null || listExpected.size() == 0)) {
AssertionUtils.jettAssertTrue("Actual object is empty " + actualObject + " " +
", checking if Expected Object is empty. Expected object " + expectedObject, true);
return false;
}
// size of the containers's are not same
if (setActual.size() != listExpected.size()) {
AssertionUtils.jettAssertFalse("Actual object is " + setActual + " " +
", checking if Expected entry size is not equal. Expected object " + listExpected, true);
return false;
}
return true;
}
/**
* compares expected and actual set elements according to their respective get*() function return values
*
*
* @param expected
* @param actual
* @return
*/
private void validateSets(Set expected, Set actual)
throws AssertionError {
Object nextExpected;
Iterator iterExpected = expected.iterator();
while (iterExpected.hasNext()) {
nextExpected = iterExpected.next();
validateObjects(actual, nextExpected);
}
}
/**
* compares given two primitive objects by calling {@link Object#equals(Object)} } function of expected object
*
* @param actual
* @param expected
* @return
*/
private boolean validatePrimitiveField(Object actual, Object expected) {
if ((expected == null) && (actual == null)) {
return true;
}
if (!expected.toString().equals(actual.toString())) {
return false;
}
return true;
}
/**
* invokes given method for each actual and expected object,
* then delegates to {@link #validatePrimitiveField(Object, Object)} to check reflected values
*
* @param method
* @param actual
* @param expected
* @return
* @throws AssertionError
*/
private boolean validatePrimitiveField(Method method, Object actual, Object expected) throws AssertionError {
// check primitive values
Object expReflected = Reflect.invokeReflection(method, expected, new Object[]{});
Object actReflected = Reflect.invokeReflection(method, actual, new Object[]{});
return validatePrimitiveField(actReflected, expReflected);
}
/**
* validation entry point for Complex java types (BEAN)
* invokes given method for each actual and expected object, after null checks,
* execution is delegated to
*
* @param method
* @param actual
* @param expected
* @return
* @throws AssertionError
*/
private boolean validateBeanField(Method method, Object actual, Object expected)
throws AssertionError {
Object expReflected = Reflect.invokeReflection(method, expected, new Object[]{});
Object actReflected = Reflect.invokeReflection(method, actual, new Object[]{});
if ((expReflected == null) && (actReflected == null)) {
return true;
}
if (expReflected == null && actReflected != null) {
return false;
}
if (expReflected != null && actReflected == null) {
return false;
}
if (expReflected == actReflected) {
return true;
}
if (expReflected.equals(actReflected)) {
return true;
}
// check if it is a collection
if (Collection.class.isAssignableFrom(method.getReturnType())) {
throw new AssertionError("Set of collections are not implemented so cannot check collection types, " +
"Actual value " + actReflected + " Expected value "+ expReflected );
}
if (Map.class.isAssignableFrom(method.getReturnType())) {
throw new AssertionError("Set of maps are not implemented so cannot check collection types, " +
"Actual value " + actReflected + " Expected value "+ expReflected );
}
return validateObject(actReflected, expReflected);
}
/**
* main validation entry point for get*() functions, validation is branched according to method returnType,
* validation is forwarded to whether
*
* @param method
* @param actual
* @param expected
* @return
* @throws AssertionError
*/
private boolean validateField(Method method, Object actual, Object expected)
throws AssertionError {
DataTypeEnum type = ObjectUtil.resolveType(method.getReturnType());
if (!type.equals(DataTypeEnum.JAVA_DOT_LANG_OBJECT) && !type.equals(DataTypeEnum.STRING)
&& !method.getReturnType().isPrimitive()) {
return validateBeanField(method, actual, expected);
}
return validatePrimitiveField(method, actual, expected);
}
/**
* main validation function for expected/actual object comparison
* if actual object is primitive, java.dot.lang.* or String then {@link #validatePrimitiveField(Object, Object)} is
* used for comparison, otherwise get* () methods of expected object are used for comparison
*
* @param actual
* @param expected
* @return true if every get*() function value of expected object matches to each field value of actual object respectively.
* @throws AssertionError
*/
private boolean validateObject(Object actual, Object expected)
throws AssertionError {
// check if primitive or String or java lang object
DataTypeEnum type = ObjectUtil.resolveType(actual.getClass());
if (type.equals(DataTypeEnum.JAVA_DOT_LANG_OBJECT) || type.equals(DataTypeEnum.STRING)
|| actual.getClass().isPrimitive()) {
return validatePrimitiveField(actual, expected);
}
Method[] expectedMethods = actual.getClass().getDeclaredMethods();
Class clazz = actual.getClass();
// check every field matches
// condition every field value must match
boolean isMatched = false;
for (Method m : expectedMethods) {
int numberOfArgs = m.getParameterTypes().length;
String methodName = m.getName();
if ((numberOfArgs != 0) || (!methodName.startsWith("get"))) {
continue;
}
if (!matchFieldName(methodName, clazz)) {
// raise a failure
AssertionUtils.jettAssertFalse(" Expected field name " + methodName + " not found ", true);
return false;
}
isMatched = validateField(m, actual, expected);
if (!isMatched) {
return false;
}
}
return isMatched;
}
/**
* tries to to find a similiar object of expected one in the given actual list
*
*
* @param actual
* @param expected
* @throws AssertionError
*/
private void validateObjects(Set actual, Object expected)
throws AssertionError {
Iterator iterActual = actual.iterator();
boolean valuesMatched = false;
Object nextElement;
while (iterActual.hasNext()) {
nextElement = iterActual.next();
valuesMatched = validateObject(nextElement, expected);
if (!valuesMatched) {
continue;
}
AssertionUtils.jettAssertTrue("Expected object " + expected + " found in the actual set " +
actual, true);
return;
}
if (!valuesMatched) {
AssertionUtils.jettAssertFalse("Expected object " + expected + " not found in the actual set " +
actual, true);
}
}
/**
* checks a given property name exist in the given clazz
*
* @param propertyName
* @param clazz a class to scan
* @return true if, given propertyName matches one of the clazz's properties, otherwise false
*/
private boolean matchFieldName(String propertyName, Class clazz) {
//check existance of the method name in the actual object
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
int numberOfArgs = m.getParameterTypes().length;
String methodName = m.getName();
if ((numberOfArgs != 0) || (!methodName.startsWith("get"))) {
continue;
}
if (!methodName.equals(propertyName)) {
continue;
}
return true;
}
return false;
}
/**
*
* @param message
* @param expectedObject
* @param actualObject
* @param expectedMetaData
* @throws AssertionError
*/
@Override
public void execute(String message, Object expectedObject, Object actualObject,
AggregatedStepExpectedOutputMetaData expectedMetaData)
throws AssertionError {
//do some initial checks going further
if (!doNullAndSizeCheck(expectedObject, actualObject)) {
return;
}
Set setActual = (Set) actualObject;
Set setExpected = (Set) expectedObject;
validateSets(setExpected, setActual);
}
}