blob: d44b1e3a9b5cb6787b7431f497cbc7aa81c9eea0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
<<<<<<< Updated upstream
*
* 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
=======
*
* https://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
>>>>>>> Stashed changes
* limitations under the License.
*/
package org.apache.jdo.tck.query;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.jdo.JDOException;
import javax.jdo.JDOFatalInternalException;
import javax.jdo.JDOQLTypedQuery;
import javax.jdo.JDOUserException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import org.apache.jdo.tck.AbstractReaderTest;
import org.apache.jdo.tck.pc.company.CompanyModelReader;
import org.apache.jdo.tck.pc.mylib.MylibReader;
import org.apache.jdo.tck.pc.mylib.PCPoint;
import org.apache.jdo.tck.pc.mylib.PrimitiveTypes;
import org.apache.jdo.tck.util.ConversionHelper;
import org.apache.jdo.tck.util.EqualityHelper;
public abstract class QueryTest extends AbstractReaderTest {
/** */
public static final String SERIALZED_QUERY = "query.ser";
/** */
public static final String COMPANY_TESTDATA =
"org/apache/jdo/tck/pc/company/companyForQueryTests.xml";
/** */
public static final String MYLIB_TESTDATA = "org/apache/jdo/tck/pc/mylib/mylibForQueryTests.xml";
/** List of inserted instances (see methods insertPCPoints and getFromInserted). */
protected final List<PCPoint> inserted = new ArrayList<>();
/**
* The company model reader is used to read company model instances from an XML file. Instances
* refered by this reader are made persistent by {@link
* QueryTest#loadAndPersistCompanyModel(PersistenceManager)}.
*/
private CompanyModelReader companyModelReaderForPersistentInstances;
/**
* The company model reader is used to read company model instances from an XML file. Instances
* refered by this reader remain transient.
*/
private CompanyModelReader companyModelReaderForTransientInstances;
/**
* The mylib reader is used to read mylib instances from an XML file. Instances refered by this
* reader are made persistent by {@link QueryTest#loadAndPersistMylib(PersistenceManager)}.
*/
private MylibReader mylibReaderForPersistentInstances;
/**
* The mylib reader is used to read mylib instances from an XML file. Instances refered by this
* reader are made persistent by
*/
private MylibReader mylibReaderForTransientInstances;
// Helper methods to create persistent PCPoint instances
/**
* @param pm the PersistenceManager
*/
public void loadAndPersistPCPoints(PersistenceManager pm) {
insertPCPoints(pm, 5);
}
/**
* @param pm the PersistenceManager
* @param numInsert number of instances to be inserted
*/
protected void insertPCPoints(PersistenceManager pm, int numInsert) {
Transaction tx = pm.currentTransaction();
try {
tx.begin();
for (int i = 0; i < numInsert; i++) {
PCPoint pc = new PCPoint(i, i);
pm.makePersistent(pc);
inserted.add(pc);
}
tx.commit();
tx = null;
if (debug) logger.debug("Total objects inserted : " + numInsert);
} finally {
if ((tx != null) && tx.isActive()) tx.rollback();
}
}
/**
* @param list list
* @return list
*/
public List<PCPoint> getFromInserted(List<PCPoint> list) {
if (list == null) return null;
List<PCPoint> result = new ArrayList<>();
for (PCPoint pc : list) {
for (PCPoint pci : inserted) {
if (pc.getX() == pci.getX()) {
result.add(pci);
break;
}
}
}
return result;
}
// Company model and mylib helper methods
/**
* Returns the name of the company test data resource.
*
* @return name of the company test data resource.
*/
protected String getCompanyTestDataResource() {
return COMPANY_TESTDATA;
}
/**
* Initializes and returns the company model reader for persistent instances.
*
* @return the company model reader for persistent instances.
*/
private CompanyModelReader getCompanyModelReaderForPersistentInstances() {
if (companyModelReaderForPersistentInstances == null) {
companyModelReaderForPersistentInstances =
new CompanyModelReader(getCompanyTestDataResource());
}
return companyModelReaderForPersistentInstances;
}
/**
* Initializes and returns the company model reader for transient instances.
*
* @return the company model reader for transient instances.
*/
private CompanyModelReader getCompanyModelReaderForTransientInstances() {
if (companyModelReaderForTransientInstances == null) {
companyModelReaderForTransientInstances =
new CompanyModelReader(getCompanyTestDataResource());
}
return companyModelReaderForTransientInstances;
}
/**
* Initializes and returns the mylib reader for persistent instances.
*
* @return the mylib reader for persistent instances.
*/
private MylibReader getMylibReaderForPersistentInstances() {
if (mylibReaderForPersistentInstances == null) {
mylibReaderForPersistentInstances = new MylibReader(MYLIB_TESTDATA);
}
return mylibReaderForPersistentInstances;
}
/**
* Initializes and returns the mylib reader for transient instances.
*
* @return the mylib reader for transient instances.
*/
private MylibReader getMylibReaderForTransientInstances() {
if (mylibReaderForTransientInstances == null) {
mylibReaderForTransientInstances = new MylibReader(MYLIB_TESTDATA);
}
return mylibReaderForTransientInstances;
}
/**
* Reads a graph of company model objects from the internal reader. This methods explictly calls
* makePersistent for all named instances using the specified PersistenceManager. The method
* returns the CompanyModelReader instance allowing to access a compay model instance by name.
*
* @param pm the PersistenceManager
* @return the company model reader
*/
public CompanyModelReader loadAndPersistCompanyModel(PersistenceManager pm) {
makePersistentAll(pm, getRootList(getCompanyModelReaderForPersistentInstances()));
return getCompanyModelReaderForPersistentInstances();
}
/**
* Reads a graph of mylib objects from the internal reader. This methods explictly calls
* makePersistent for all named instances using the specified PersistenceManager. The method
* returns the CompanyModelReader instance allowing to access a compay model instance by name.
*
* @param pm the PersistenceManager
* @return mylib reader
*/
public MylibReader loadAndPersistMylib(PersistenceManager pm) {
makePersistentAll(pm, getRootList(getMylibReaderForPersistentInstances()));
return getMylibReaderForPersistentInstances();
}
/**
* Persists the given pc instances.
*
* @param pm the PersistenceManager
* @param pcInstances the pc instances to persist
*/
private void makePersistentAll(PersistenceManager pm, List<?> pcInstances) {
Transaction tx = pm.currentTransaction();
tx.begin();
try {
pm.makePersistentAll(pcInstances);
if (debug) logger.debug("inserted " + pcInstances);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
}
/**
* Returns a persistent company model instance for the given bean name.
*
* @param clazz class of the returned instance
* @param beanName the bean name.
* @return the persistent company model instance.
*/
protected <T> T getPersistentCompanyModelInstance(Class<T> clazz, String beanName) {
return beanName == null
? null
: getBean(getCompanyModelReaderForPersistentInstances(), clazz, beanName);
}
/**
* Returns a transient company model instance for the given bean name.
*
* @param clazz the class of teh returned instance
* @param beanName the bean name.
* @return the transient company model instance.
*/
protected <T> T getTransientCompanyModelInstance(Class<T> clazz, String beanName) {
return beanName == null
? null
: getBean(getCompanyModelReaderForTransientInstances(), clazz, beanName);
}
/**
* Returns a list of persistent company model instances for beans names in the given argument.
*
* @param elementType the element type of the returned list
* @param beanNames the bean names of company model instances.
* @return the list of persistent company model instances.
*/
protected <T> List<T> getPersistentCompanyModelInstancesAsList(
Class<T> elementType, String... beanNames) {
List<T> result = new ArrayList<>(beanNames.length);
for (String beanName : beanNames) {
result.add(getPersistentCompanyModelInstance(elementType, beanName));
}
return result;
}
/**
* Returns a list of transient company model instances for beans names in the given argument.
*
* @param elementType the element type of the returned list
* @param beanNames the bean names of company model instances.
* @return the list of transient company model instances.
*/
protected <T> List<T> getTransientCompanyModelInstancesAsList(
Class<T> elementType, String... beanNames) {
List<T> result = new ArrayList<>(beanNames.length);
for (String beanName : beanNames) {
result.add(getTransientCompanyModelInstance(elementType, beanName));
}
return result;
}
/**
* Returns a persistent mylib instance for the given bean name.
*
* @param beanName the bean name.
* @return the persistent mylib instance.
*/
protected Object getPersistentMylibInstance(String beanName) {
return beanName == null ? null : getBean(getMylibReaderForPersistentInstances(), beanName);
}
/**
* Returns a transient mylib instance for the given bean name.
*
* @param beanName the bean name.
* @return the transient mylib instance.
*/
protected Object getTransientMylibInstance(String beanName) {
return beanName == null ? null : getBean(getMylibReaderForTransientInstances(), beanName);
}
/**
* Returns an array of persistent mylib instances for beans names in the given argument.
*
* @param beanNames the bean names of mylib instances.
* @return the array of persistent mylib instances.
*/
protected Object[] getPersistentMylibInstances(String... beanNames) {
Object[] result = new Object[beanNames.length];
for (int i = 0; i < beanNames.length; i++) {
result[i] = getPersistentMylibInstance(beanNames[i]);
}
return result;
}
/**
* Returns an array of transient mylib instances for beans names in the given argument.
*
* @param beanNames the bean names of mylib instances.
* @return the array of transient mylib instances.
*/
protected Object[] getTransientMylibInstances(String... beanNames) {
Object[] result = new Object[beanNames.length];
for (int i = 0; i < beanNames.length; i++) {
result[i] = getTransientMylibInstance(beanNames[i]);
}
return result;
}
/**
* Returns a list of persistent mylib instances for beans names in the given argument.
*
* @param beanNames the bean names of mylib instances.
* @return the list of persistent mylib instances.
*/
protected List<Object> getPersistentMylibInstancesAsList(String... beanNames) {
return Arrays.asList(getPersistentMylibInstances(beanNames));
}
/**
* Returns a list of transient mylib instances for beans names in the given argument.
*
* @param beanNames the bean names of mylib instances.
* @return the list of transient mylib instances.
*/
protected List<Object> getTransientMylibInstancesAsList(String... beanNames) {
return Arrays.asList(getTransientMylibInstances(beanNames));
}
// PrimitiveTypes helper methods (creation and query)
/**
* @param pm the PersistenceManager
*/
public void loadAndPersistPrimitiveTypes(PersistenceManager pm) {
insertPrimitiveTypes(pm);
}
/**
* @param pm the PersistenceManager
*/
protected void insertPrimitiveTypes(PersistenceManager pm) {
boolean bFlag = false;
String strValue = "";
char charValue = '\u0000';
int numInsert = 10;
Transaction tx = pm.currentTransaction();
try {
tx.begin();
for (int i = 1; i <= numInsert; i++) {
if (i % 2 == 1) {
bFlag = true;
strValue = "Odd" + i;
charValue = 'O';
} else {
bFlag = false;
strValue = "Even" + i;
charValue = 'E';
}
PrimitiveTypes primitiveObject =
new PrimitiveTypes(
i,
bFlag,
Boolean.valueOf(bFlag),
(byte) i,
Byte.valueOf((byte) i),
(short) i,
Short.valueOf((short) i),
i,
Integer.valueOf(i),
i,
Long.valueOf(i),
i,
Float.valueOf(i),
i,
Double.valueOf(i),
charValue,
Character.valueOf(charValue),
Calendar.getInstance().getTime(),
strValue,
new BigDecimal(String.valueOf(i)),
new BigInteger(String.valueOf(i)),
Long.valueOf(i));
pm.makePersistent(primitiveObject);
}
tx.commit();
tx = null;
if (debug) logger.debug("Total objects inserted : " + numInsert);
} finally {
if ((tx != null) && tx.isActive()) tx.rollback();
}
}
/**
* Creates and executes a PrimitiveTypes query with the specified filter. The method checks
* whether the query returns the expected result.
*
* @param filter the filter
* @param pm the PersistenceManager
* @param expected expected
* @param assertion assertion
*/
protected void runSimplePrimitiveTypesQuery(
String filter, PersistenceManager pm, Collection<PrimitiveTypes> expected, String assertion) {
Query<PrimitiveTypes> q = pm.newQuery(PrimitiveTypes.class);
q.setFilter(filter);
List<PrimitiveTypes> results = q.executeList();
if (debug) logger.debug("execute '" + filter + "' returns " + results.size() + " instance(s)");
checkQueryResultWithoutOrder(assertion, filter, results, expected);
}
/**
* Creates and executes a PrimitiveTypes query with the specified filter, parameter declarations
* and parameter values. The method checks whether the query returns the expected result.
*
* @param filter the filter
* @param paramDecl the parameter declaration
* @param paramValue the parameter values
* @param pm the PersistenceManager
* @param expected expected
* @param assertion assertion
*/
protected void runParameterPrimitiveTypesQuery(
String filter,
String paramDecl,
Object paramValue,
PersistenceManager pm,
Collection<PrimitiveTypes> expected,
String assertion) {
Query<PrimitiveTypes> q = pm.newQuery(PrimitiveTypes.class);
q.setFilter(filter);
q.declareParameters(paramDecl);
q.setParameters(paramValue);
List<PrimitiveTypes> results = q.executeList();
if (debug)
logger.debug(
"execute '"
+ filter
+ "' with param '"
+ paramValue
+ "' returns "
+ results.size()
+ " instance(s)");
checkQueryResultWithoutOrder(assertion, filter, results, expected);
}
// Helper methods to check query result
/**
* Verify that expected equals result, including the order of the elements. If not equal, fail the
* test. If there is a filter != null, do not use this method. Use the method of the same name
* that takes a String as the second argument.
*
* @param assertion assertion
* @param result the result
* @param expected the expected
*/
protected void checkQueryResultWithOrder(String assertion, Object result, Object expected) {
if (!equals(result, expected)) {
queryFailed(assertion, "null", result, expected);
}
}
/**
* Verify that expected equals result, ignoring the order of the elements. If not equal, fail the
* test. If there is a filter != null, do not use this method. Use the method of the same name
* that takes a String as the second argument.
*
* @param assertion assertion
* @param result the result
* @param expected the expected
*/
protected void checkQueryResultWithoutOrder(String assertion, Object result, Object expected) {
// We need to explicitly check on collections passed as parameters
// because equals(Object, Object) checks on lists and afterwards
// on collections. This ensures, lists are compared without order
// for queries without an ordering specification.
if (result instanceof Collection && expected instanceof Collection) {
if (!equalsCollection((Collection<?>) result, (Collection<?>) expected)) {
queryFailed(assertion, "null", result, expected);
}
} else {
if (!equals(result, expected)) {
queryFailed(assertion, "null", result, expected);
}
}
}
private void queryFailed(String assertion, String query, Object result, Object expected) {
String lf = System.getProperty("line.separator");
result = ConversionHelper.convertObjectArrayElements(result);
expected = ConversionHelper.convertObjectArrayElements(expected);
fail(
assertion,
"Wrong query result: "
+ lf
+ "query: "
+ query
+ lf
+ "expected: "
+ expected.getClass().getName()
+ " of size "
+ size(expected)
+ lf
+ expected
+ lf
+ "got: "
+ result.getClass().getName()
+ " of size "
+ size(result)
+ lf
+ result);
}
/**
* @param assertion assertion
* @param query the query
* @param result the result
* @param expected expected
*/
protected void checkQueryResultWithOrder(
String assertion, String query, Object result, Object expected) {
if (!equals(result, expected)) {
queryFailed(assertion, query, result, expected);
}
}
/**
* @param assertion assertion
* @param query the query
* @param result the result
* @param expected expected
*/
protected void checkQueryResultWithoutOrder(
String assertion, String query, Object result, Object expected) {
// We need to explicitly check on collections passed as parameters
// because equals(Object, Object) checks on lists and afterwards
// on collections. This ensures, lists are compared without order
// for queries without an ordering specification.
if (result instanceof Collection && expected instanceof Collection) {
if (!equalsCollection((Collection<?>) result, (Collection<?>) expected)) {
queryFailed(assertion, query, result, expected);
}
} else {
if (!equals(result, expected)) {
queryFailed(assertion, query, result, expected);
}
}
}
/**
* Returns <code>true</code> if <code>o1</code> and <code>o2</code> equal. This method is capable
* to compare object arrays, collections of object arrays, maps of object arrays. This method
* implements a narrowing in case of floating point values. In case of big decimals it calls
* {@link BigDecimal#compareTo(java.math.BigDecimal)}. It allows <code>o1</code> and/or <code>o2
* </code> to be <code>null</code>.
*
* @param o1 the first object
* @param o2 the second object
* @return <code>true</code> if <code>o1</code> and <code>o2</code> equal.
*/
protected boolean equals(Object o1, Object o2) {
boolean result;
if (o1 == o2) {
result = true;
} else if ((o1 instanceof Object[]) && (o2 instanceof Object[])) {
result = equalsObjectArray((Object[]) o1, (Object[]) o2);
} else if ((o1 instanceof List) && (o2 instanceof List)) {
result = equalsList((List<?>) o1, (List<?>) o2);
} else if ((o1 instanceof Collection) && (o2 instanceof Collection)) {
result = equalsCollection((Collection<?>) o1, (Collection<?>) o2);
} else if ((o1 instanceof Map) && (o2 instanceof Map)) {
result = equalsMap((Map<?, ?>) o1, (Map<?, ?>) o2);
} else if ((o1 instanceof Float) && (o2 instanceof Float)) {
result = closeEnough(((Float) o1).floatValue(), ((Float) o2).floatValue());
} else if ((o1 instanceof Double) && (o2 instanceof Double)) {
result = closeEnough(((Double) o1).floatValue(), ((Double) o2).floatValue());
} else if ((o1 instanceof BigDecimal) && (o2 instanceof BigDecimal)) {
result = ((BigDecimal) o1).compareTo((BigDecimal) o2) == 0;
} else if (o1 != null) {
result = o1.equals(o2);
} else {
// Due to the first if and due to the last if, we have:
// o1 == null && o2 != null
result = false;
}
return result;
}
/**
* Returns <code>true</code> if <code>o1</code> and <code>o2</code> equal. This method iterates
* over both object arrays and calls {@link QueryTest#equals(Object, Object)} passing
* corresponding instances. {@link QueryTest#equals(Object, Object)} is called rather than {@link
* Object#equals(java.lang.Object)} because object arrays having equal elements cannot be compared
* calling {@link Object#equals(java.lang.Object)}. This method does not allow <code>o1</code> and
* <code>o2</code> to be <code>null</code> both.
*
* @param o1 the first object array
* @param o2 the second object array
* @return <code>true</code> if <code>o1</code> and <code>o2</code> equal.
*/
protected boolean equalsObjectArray(Object[] o1, Object[] o2) {
boolean result = true;
if (o1 != o2) {
if (o1.length != o2.length) {
result = false;
} else {
for (int i = 0; i < o1.length; i++) {
if (!equals(o1[i], o2[i])) {
result = false;
break;
}
}
}
}
return result;
}
/**
* Returns <code>true</code> if <code>o1</code> and <code>o2</code> equal. This method iterates
* both lists and calls {@link QueryTest#equals(Object, Object)} on corresponding elements. {@link
* QueryTest#equals(Object, Object)} is called rather than {@link Object#equals(java.lang.Object)}
* because object arrays having equal elements cannot be compared calling {@link
* Object#equals(java.lang.Object)}. This method does not allow <code>o1</code> and <code>o2
* </code> to be <code>null</code> both.
*
* @param o1 the first list
* @param o2 the second list
* @return <code>true</code> if <code>o1</code> and <code>o2</code> equal.
*/
protected <S, T> boolean equalsList(List<S> o1, List<T> o2) {
boolean result = true;
if (o1 != o2) {
if (o1.size() != o2.size()) {
result = false;
} else {
Iterator<S> i = o1.iterator();
Iterator<T> ii = o2.iterator();
while (i.hasNext()) {
Object firstObject = i.next();
Object secondObject = ii.next();
if (!equals(firstObject, secondObject)) {
result = false;
break;
}
}
}
}
return result;
}
/**
* Returns <code>true</code> if <code>o1</code> and <code>o2</code> equal. This method iterates
* over the first collection and checks if each instance is contained in the second collection by
* calling remove(Collection, Object). This guarantees that the cardinality of each instance in
* the first collection matches the cardinality of each instance in the second collection. This
* method does not allow <code>o1</code> and <code>o2</code> to be <code>null</code> both.
*
* @param o1 the first collection
* @param o2 the second collection
* @return <code>true</code> if <code>o1</code> and <code>o2</code> equal.
*/
protected <S, T> boolean equalsCollection(Collection<S> o1, Collection<T> o2) {
// make a copy of o2 so we can destroy it
Collection<T> o2copy = new ArrayList<>();
for (T t : o2) {
o2copy.add(t);
}
boolean result = true;
if (o1 != o2) {
if (o1.size() != o2.size()) {
result = false;
} else {
for (S oo1 : o1) {
if (!remove(o2copy, oo1)) {
result = false;
break;
}
}
}
}
return result;
}
/**
* Returns <code>true</code> if <code>o1</code> and <code>o2</code> equal. This method checks if
* the key sets and the value sets of both maps equal calling equalsCollection(Collection,
* Collection). This method does not allow <code>o1</code> and <code>o2</code> to be <code>null
* </code> both.
*
* @param o1 the first map
* @param o2 the second map
* @return <code>true</code> if <code>o1</code> and <code>o2</code> equal.
*/
protected <K1, V1, K2, V2> boolean equalsMap(Map<K1, V1> o1, Map<K2, V2> o2) {
boolean result = true;
if (o1 != o2) {
if (o1.size() != o2.size()) {
result = false;
} else {
for (Map.Entry<K1, V1> entry : o1.entrySet()) {
K1 key = entry.getKey();
V1 value = entry.getValue();
V2 value2 = o2.get(key);
if (!equals(value, value2)) {
result = false;
break;
}
}
}
}
return result;
}
/**
* Returns <code>true</code> if <code>o</code> is contained in the given collection. This method
* iterates the given collection and calls {@link QueryTest#equals(Object, Object)} for each
* instance. {@link QueryTest#equals(Object, Object)} is called rather than {@link
* Object#equals(java.lang.Object)} because object arrays having equal elements cannot be compared
* calling {@link Object#equals(java.lang.Object)}.
*
* @param col the collection
* @param o the object
* @return <code>true</code> if <code>o</code> is contained in the given collection.
*/
private boolean contains(Collection<Object> col, Object o) {
for (Object value : col) {
if (equals(o, value)) {
return true;
}
}
return false;
}
/**
* Returns <code>true</code> if <code>o</code> is contained in the given collection and was
* removed. This method iterates the given collection and calls {@link QueryTest#equals(Object,
* Object)} for each instance. {@link QueryTest#equals(Object, Object)} is called rather than
* {@link Object#equals(java.lang.Object)} because object arrays having equal elements cannot be
* compared calling {@link Object#equals(java.lang.Object)}.
*
* @param col the collection
* @param o the object
* @return <code>true</code> if <code>o</code> is contained in the given collection and was
* removed.
*/
private <T> boolean remove(Collection<T> col, Object o) {
for (Iterator<T> i = col.iterator(); i.hasNext(); ) {
if (equals(o, i.next())) {
i.remove();
return true;
}
}
return false;
}
/**
* Returns <code>true</code> if the specified float values are close enough to be considered to be
* equal for a deep equals comparison. Floating point values are not exact, so comparing them
* using <code>==</code> might not return useful results. This method checks that both double
* values are within some percent of each other.
*
* @param d1 one double to be tested for close enough
* @param d2 the other double to be tested for close enough
* @return <code>true</code> if the specified values are close enough.
*/
public boolean closeEnough(double d1, double d2) {
if (d1 == d2) return true;
double diff = Math.abs(d1 - d2);
return diff < Math.abs((d1 + d2) * EqualityHelper.DOUBLE_EPSILON);
}
/**
* Returns <code>true</code> if the specified float values are close enough to be considered to be
* equal for a deep equals comparison. Floating point values are not exact, so comparing them
* using <code>==</code> might not return useful results. This method checks that both float
* values are within some percent of each other.
*
* @param f1 one float to be tested for close enough
* @param f2 the other float to be tested for close enough
* @return <code>true</code> if the specified values are close enough.
*/
public boolean closeEnough(float f1, float f2) {
if (f1 == f2) return true;
float diff = Math.abs(f1 - f2);
return diff < Math.abs((f1 + f2) * EqualityHelper.FLOAT_EPSILON);
}
/**
* Returns the size of the object. If it is a multivalued object (Collection, Map, or array)
* return the number of elements. If not, return 1.
*
* @param o the object
* @return size
*/
protected int size(Object o) {
if (o instanceof Collection) {
return ((Collection<?>) o).size();
}
if (o instanceof Object[]) {
return ((Object[]) o).length;
}
if (o instanceof Map) {
return ((Map<?, ?>) o).size();
}
return 1;
}
// Debugging helper methods
/**
* @param results the results
* @param expected expected values
*/
protected void printOutput(Object results, Collection<?> expected) {
if (!debug) return;
Iterator<?> iter = null;
PCPoint pcp = null;
if (results == null) {
logger.debug("Query returns null");
return;
}
if (!(results instanceof Collection)) {
logger.debug(
"Query result is not a collection: "
+ (results == null ? "null" : results.getClass().getName()));
}
logger.debug("Retrived Objects are:");
iter = ((Collection<?>) results).iterator();
while (iter.hasNext()) {
pcp = (PCPoint) iter.next();
logger.debug("X = " + pcp.getX() + "\tY = " + pcp.getY());
}
logger.debug("Expected Objects are:");
iter = expected.iterator();
while (iter.hasNext()) {
pcp = (PCPoint) iter.next();
logger.debug("X = " + pcp.getX() + "\tY = " + pcp.getY());
}
}
// compile query methods
/**
* Compiles the given query element holder instance as a JDO API query. Argument <code>positive
* </code> determines if the compilation is supposed to succeed or to fail. If <code>true</code>
* and the compilation fails, then the test case fails prompting argument <code>assertion</code>.
* If <code>false</code> and the compilation succeeds, then the test case fails prompting argument
* <code>assertion</code>. Otherwise the test case succeeds.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param positive determines if the compilation is supposed to succeed or to fail.
*/
protected void compileAPIQuery(
String assertion, QueryElementHolder<?> queryElementHolder, boolean positive) {
if (logger.isDebugEnabled()) {
logger.debug("Compiling API query: " + queryElementHolder);
}
compile(assertion, queryElementHolder, false, queryElementHolder.toString(), positive);
}
/**
* Compiles the given query element holder instance as a JDO single string query. Argument <code>
* positive</code> determines if the compilation is supposed to succeed or to fail. If <code>true
* </code> and the compilation fails, then the test case fails prompting argument <code>assertion
* </code>. If <code>false</code> and the compilation succeeds, then the test case fails prompting
* argument <code>assertion</code>. Otherwise the test case succeeds.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param positive determines if the compilation is supposed to succeed or to fail.
*/
protected void compileSingleStringQuery(
String assertion, QueryElementHolder<?> queryElementHolder, boolean positive) {
if (logger.isDebugEnabled())
logger.debug("Compiling single string query: " + queryElementHolder);
compile(assertion, queryElementHolder, true, queryElementHolder.toString(), positive);
}
/**
* Compiles the given single string query. Argument <code>positive</code> determines if the
* compilation is supposed to succeed or to fail. If <code>true</code> and the compilation fails,
* then the test case fails prompting argument <code>assertion</code>. If <code>false</code> and
* the compilation succeeds, then the test case fails prompting argument <code>assertion</code>.
* Otherwise the test case succeeds.
*
* @param assertion the assertion to prompt if the test case fails.
* @param singleStringQuery the single string query
* @param positive determines if the compilation is supposed to succeed or to fail.
*/
protected void compileSingleStringQuery(
String assertion, String singleStringQuery, boolean positive) {
if (logger.isDebugEnabled())
logger.debug("Compiling single string query: " + singleStringQuery);
compile(assertion, null, true, singleStringQuery, positive);
}
/**
* Compiles the given query element holder instance as a JDO API query or single string query,
* depending on argument <code>asSingleString</code>. Argument <code>singleStringQuery</code> is
* used to support queries which cannot be expressed as query element holder instances. That
* argument is ignored if argument <code>queryElementHolder</code> is set. Argument <code>positive
* </code> determines if the compilation is supposed to succeed or to fail. If <code>true</code>
* and the compilation fails, then the test case fails prompting argument <code>assertion</code>.
* If <code>false</code> and the compilation succeeds, then the test case fails prompting argument
* <code>assertion</code>. Otherwise the test case succeeds.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to compile.
* @param asSingleString determines if the query specified by <code>queryElementHolder</code> is
* compiled as single string query or as API query.
* @param singleStringQuery the query to compile as a JDO single string query if there is no query
* element holder.
* @param positive determines if the compilation is supposed to succeed or to fail.
*/
private void compile(
String assertion,
QueryElementHolder<?> queryElementHolder,
boolean asSingleString,
String singleStringQuery,
boolean positive) {
getPM();
try {
Query<?> query;
if (queryElementHolder != null) {
if (asSingleString) {
query = queryElementHolder.getSingleStringQuery(pm);
} else {
query = queryElementHolder.getAPIQuery(pm);
}
} else {
query = getPM().newQuery(singleStringQuery);
}
compile(assertion, query, singleStringQuery, positive);
} catch (JDOUserException e) {
// This exception handler considers a JDOUserException
// to be thrown in newQuery methods.
// A JDOUserException may be expected in case of negative tests.
if (positive) {
fail(
assertion
+ "Query '"
+ queryElementHolder
+ "' must be compilable. The exception message is: "
+ e.getMessage());
}
}
}
/**
* Compiles the given query instance. Argument <code>positive</code> determines if the compilation
* is supposed to succeed or to fail. If <code>true</code> and the compilation fails, then the
* test case fails prompting arguments <code>assertion</code> and <code>queryText</code>. If
* <code>false</code> and the compilation succeeds, then the test case fails prompting argument
* <code>assertion</code> and <code>queryText</code>. Otherwise the test case succeeds.
*
* @param assertion asserting text
* @param query the query instance
* @param queryText the text representation of the query
* @param positive positive test
*/
protected void compile(String assertion, Query<?> query, String queryText, boolean positive) {
getPM();
Transaction tx = pm.currentTransaction();
tx.begin();
try {
query.compile();
if (!positive) {
fail(assertion, "Query compilation must throw JDOUserException: " + queryText);
}
} catch (JDOUserException e) {
if (positive) {
fail(
assertion,
"Query '"
+ queryText
+ "' must be compilable. The exception message is: "
+ e.getMessage());
}
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
}
// execute query methods
/**
* Executes the given query element holder instance as a JDO API query. The result of that query
* is compared against the given argument <code>expectedResult</code>. If the expected result does
* not match the returned query result, then the test case fails prompting argument <code>
* assertion</code>.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param expectedResult the expected query result.
*/
protected void executeAPIQuery(
String assertion, QueryElementHolder<?> queryElementHolder, Object expectedResult) {
if (logger.isDebugEnabled()) {
logger.debug("Executing API query: " + queryElementHolder);
}
execute(assertion, queryElementHolder, false, expectedResult);
}
/**
* Executes the given query element holder instance as a JDO single string query. The result of
* that query is compared against the given argument <code>expectedResult</code>. If the expected
* result does not match the returned query result, then the test case fails prompting argument
* <code>assertion</code>.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param expectedResult the expected query result.
*/
protected void executeSingleStringQuery(
String assertion, QueryElementHolder<?> queryElementHolder, Object expectedResult) {
if (logger.isDebugEnabled())
logger.debug("Executing single string query: " + queryElementHolder);
execute(assertion, queryElementHolder, true, expectedResult);
}
/**
* @param assertion assertion text
* @param queryElementHolder query elements
* @param expectedResult expected query result
*/
protected void executeJDOQLTypedQuery(
String assertion, QueryElementHolder<?> queryElementHolder, Object expectedResult) {
executeJDOQLTypedQuery(assertion, queryElementHolder, null, false, expectedResult);
}
/**
* @param assertion assertion text
* @param queryElementHolder query elements
* @param resultClass result class object
* @param resultClauseSpecified flag whether result clause is specified
* @param expectedResult the expected query result
*/
protected void executeJDOQLTypedQuery(
String assertion,
QueryElementHolder<?> queryElementHolder,
Class<?> resultClass,
boolean resultClauseSpecified,
Object expectedResult) {
String singleStringQuery = queryElementHolder.toString();
getPM();
Transaction tx = pm.currentTransaction();
tx.begin();
try {
JDOQLTypedQuery<?> query = queryElementHolder.getJDOQLTypedQuery();
if (query == null) {
throw new JDOFatalInternalException("Missing JDOQLTyped instance");
}
Object result = null;
try {
Map<String, ?> paramValues = queryElementHolder.getParamValues();
if (paramValues != null) {
query.setParameters(paramValues);
}
if (resultClass != null) {
if (queryElementHolder.isUnique()) {
// result class specified and unique result
result = query.executeResultUnique(resultClass);
} else {
// result class specified and list result
result = query.executeResultList(resultClass);
}
} else if (resultClauseSpecified) {
if (queryElementHolder.isUnique()) {
// result class specified and unique result
result = query.executeResultUnique();
} else {
// result class specified and list result
result = query.executeResultList();
}
} else {
if (queryElementHolder.isUnique()) {
// no result class and unique result
result = query.executeUnique();
} else {
// no result class and list result
result = query.executeList();
}
}
if (logger.isDebugEnabled()) {
logger.debug("Query result: " + ConversionHelper.convertObjectArrayElements(result));
}
checkResult(
assertion,
singleStringQuery,
queryElementHolder.hasOrdering(),
result,
expectedResult,
true);
} finally {
query.close(result);
}
} catch (JDOUserException e) {
String msg = "JDOUserException thrown while executing query:\n" + singleStringQuery;
throw new JDOException(msg, e);
} catch (JDOException e) {
String msg = "JDOException thrown while executing query:\n" + singleStringQuery;
throw new JDOException(msg, e);
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
}
/**
* Converts the given query element holder instance to a JDO query instance, based on argument
* <code>asSingleString</code>. Afterwards, delegates to method {@link QueryTest#execute(String,
* Query, String, boolean, Object, Object, boolean)}.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param asSingleString determines if the query is executed as single string query or as API
* query.
* @param expectedResult the expected query result.
* @return the query result
*/
private Object execute(
String assertion,
QueryElementHolder<?> queryElementHolder,
boolean asSingleString,
Object expectedResult) {
getPM();
Query<?> query =
asSingleString
? queryElementHolder.getSingleStringQuery(pm)
: queryElementHolder.getAPIQuery(pm);
Object result =
execute(
assertion,
query,
queryElementHolder.toString(),
queryElementHolder.hasOrdering(),
queryElementHolder.getParamValues(),
expectedResult,
true);
return result;
}
/**
* Executes the given query instance delegating to execute(String, Query, String, boolean, Object,
* Object, boolean). Logs argument <code>singleStringQuery</code> if debug logging is enabled.
*
* @param assertion the assertion to prompt if the test case fails.
* @param query the query to execute.
* @param singleStringQuery the single string representation of the query. This parameter is only
* used as part of the falure message.
* @param hasOrdering indicates if the query has an ordering clause.
* @param parameters the parmaters of the query.
* @param expectedResult the expected query result.
* @param positive indicates if query execution is supposed to fail
* @return the query result
*/
protected Object executeJDOQuery(
String assertion,
Query<?> query,
String singleStringQuery,
boolean hasOrdering,
Object[] parameters,
Object expectedResult,
boolean positive) {
if (logger.isDebugEnabled()) {
logger.debug("Executing JDO query: " + singleStringQuery);
}
return execute(
assertion, query, singleStringQuery, hasOrdering, parameters, expectedResult, positive);
}
/**
* Executes the given SQL string as a JDO SQL query. The result of that query is compared against
* the given argument <code>expectedResult</code>. If the expected result does not match the
* returned query result, then the test case fails prompting argument <code>assertion</code>.
* Argument <code>unique</code> indicates, if the query is supposed to return a single result.
* Argument <code>sql</code> may contain the substring <code>"{0}"</code>. All occurences of this
* substring are replaced by the value of PMF property <code>"javax.jdo.mapping.Schema"</code>.
*
* @param assertion the assertion to prompt if the test case fails.
* @param sql the SQL string.
* @param candidateClass the candidate class.
* @param resultClass the result class.
* @param positive positive option
* @param parameters the parameters of the query.
* @param expectedResult the expected query result.
* @param unique indicates, if the query is supposed to return a single result.
*/
@SuppressWarnings("unchecked")
protected <T> void executeSQLQuery(
String assertion,
String sql,
Class<T> candidateClass,
Class<?> resultClass,
boolean positive,
Object parameters,
Object expectedResult,
boolean unique) {
String schema = getPMFProperty("javax.jdo.mapping.Schema");
sql = MessageFormat.format(sql, new Object[] {schema});
if (logger.isDebugEnabled()) logger.debug("Executing SQL query: " + sql);
Query<T> query = getPM().newQuery("javax.jdo.query.SQL", sql);
if (unique) {
query.setUnique(unique);
}
if (candidateClass != null) {
query.setClass(candidateClass);
}
if (resultClass != null) {
query.setResultClass(resultClass);
}
execute(assertion, query, sql, false, parameters, expectedResult, positive);
}
/**
* Executes the given query instance. If argument <code>parameters</code> is an object array, then
* it is passed as an argument to the method {@link Query#executeWithArray(java.lang.Object[])}.
* If argument <code>parameters</code> is a map, then it is passed as an argument to the method
* {@link Query#executeWithMap(java.util.Map)}. If argument <code>parameters</code> is a list,
* then the list elements are passed as arguments to the execute methods taking actual parameter
* values. If argument <code>parameters</code> is <code>null</code>, then method {@link
* Query#execute()} is called on the given query instance instead.
*
* <p>The result of query execution is compared against the argument <code>expectedResult</code>.
* If the two values differ, then this method throws an {@link
* junit.framework.AssertionFailedError} and the calling test case fails prompting argument <code>
* assertion</code>.
*
* <p>If argument <code>positive</code> is <code>false</code>, then the test case invoking this
* method is considered to be a negative test case. Then, query execution is expected to throw a
* {@link JDOUserException}. If query execution succeeds in this case, then this method throws an
* {@link junit.framework.AssertionFailedError} and the calling test case fails prompting argument
* <code>assertion</code>.
*
* <p>
*
* @param assertion the assertion to prompt if the test case fails.
* @param query the query to execute.
* @param singleStringQuery the single string representation of the query. This parameter is only
* used as part of the falure message.
* @param hasOrdering indicates if the query has an ordering clause.
* @param parameters the parmaters of the query.
* @param expectedResult the expected query result.
* @param positive indicates if query execution is supposed to fail
* @return the query result
*/
@SuppressWarnings("unchecked")
private Object execute(
String assertion,
Query<?> query,
String singleStringQuery,
boolean hasOrdering,
Object parameters,
Object expectedResult,
boolean positive) {
Object result = null;
getPM();
Transaction tx = pm.currentTransaction();
tx.begin();
try {
try {
if (parameters == null) {
result = query.execute();
} else if (parameters instanceof Object[]) {
result = query.executeWithArray((Object[]) parameters);
} else if (parameters instanceof Map) {
result = query.executeWithMap((Map<String, Object>) parameters);
} else if (parameters instanceof List) {
List<?> list = (List<?>) parameters;
switch (list.size()) {
case 1:
result = query.execute(list.get(0));
break;
case 2:
result = query.execute(list.get(0), list.get(1));
break;
case 3:
result = query.execute(list.get(0), list.get(1), list.get(2));
break;
default:
throw new IllegalArgumentException(
"Argument parameters is a list " + "and must have 1, 2, or 3 elements.");
}
} else {
throw new IllegalArgumentException(
"Argument parameters " + "must be instance of List, Map, Object[], or null.");
}
if (logger.isDebugEnabled()) {
logger.debug("Query result: " + ConversionHelper.convertObjectArrayElements(result));
}
checkResult(assertion, singleStringQuery, hasOrdering, result, expectedResult, positive);
} finally {
query.close(result);
}
} catch (JDOUserException e) {
if (positive) {
String msg = "JDOUserException thrown while executing query:\n" + singleStringQuery;
throw new JDOException(msg, e);
}
} catch (JDOException e) {
String msg = "JDOException thrown while executing query:\n" + singleStringQuery;
throw new JDOException(msg, e);
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
return result;
}
/**
* @param assertion the assertion to prompt if the test case fails.
* @param singleStringQuery the single string representation of the query. This parameter is only
* used as part of the falure message.
* @param hasOrdering indicates if the query has an ordering clause.
* @param positive indicates if query execution is supposed to fail
* @param result the query result.
* @param expectedResult the expected query result.
*/
private void checkResult(
String assertion,
String singleStringQuery,
boolean hasOrdering,
Object result,
Object expectedResult,
boolean positive) {
if (positive) {
if (hasOrdering) {
checkQueryResultWithOrder(assertion, singleStringQuery, result, expectedResult);
} else {
checkQueryResultWithoutOrder(assertion, singleStringQuery, result, expectedResult);
}
} else {
fail(assertion, "Query must throw JDOUserException: " + singleStringQuery);
}
}
/**
* Converts the given query element holder instance to a JDO query instance. Calls {@link
* Query#deletePersistentAll()}, or {@link Query#deletePersistentAll(java.util.Map)}, or {@link
* Query#deletePersistentAll(java.lang.Object[])} depending on the type of argument <code>
* parameters</code>. If the number of deleted objects does not match <code>
* expectedNrOfDeletedObjects</code>, then the test case fails prompting argument <code>assertion
* </code>.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param expectedNrOfDeletedObjects the expected number of deleted objects.
*/
protected void deletePersistentAllByAPIQuery(
String assertion, QueryElementHolder<?> queryElementHolder, long expectedNrOfDeletedObjects) {
if (logger.isDebugEnabled()) {
logger.debug("Deleting persistent by API query: " + queryElementHolder);
}
delete(assertion, queryElementHolder, false, expectedNrOfDeletedObjects);
}
/**
* Converts the given query element holder instance to a JDO query instance. Calls {@link
* Query#deletePersistentAll()}, or {@link Query#deletePersistentAll(java.util.Map)}, or {@link
* Query#deletePersistentAll(java.lang.Object[])} depending on the type of argument <code>
* parameters</code>. If the number of deleted objects does not match <code>
* expectedNrOfDeletedObjects</code>, then the test case fails prompting argument <code>assertion
* </code>.
*
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param expectedNrOfDeletedObjects the expected number of deleted objects.
*/
protected void deletePersistentAllBySingleStringQuery(
String assertion, QueryElementHolder<?> queryElementHolder, long expectedNrOfDeletedObjects) {
if (logger.isDebugEnabled()) {
logger.debug("Deleting persistent by single string query: " + queryElementHolder);
}
delete(assertion, queryElementHolder, true, expectedNrOfDeletedObjects);
}
/**
* Converts the given query element holder instance to a
* JDO query based on argument <code>asSingleString</code>.
* Calls {@link Query#deletePersistentAll()}, or
* {@link Query#deletePersistentAll(java.util.Map), or
* {@link Query#deletePersistentAll(java.lang.Object[])
* depending on the type of argument <code>parameters</code>.
* If the number of deleted objects does not
* match <code>expectedNrOfDeletedObjects</code>,
* then the test case fails prompting argument <code>assertion</code>.
* @param assertion the assertion to prompt if the test case fails.
* @param queryElementHolder the query to execute.
* @param asSingleString determines if the query is executed as
* single string query or as API query.
* @param parameters the parmaters of the query.
* @param expectedNrOfDeletedObjects the expected number of deleted objects.
*/
private void delete(
String assertion,
QueryElementHolder<?> queryElementHolder,
boolean asSingleString,
long expectedNrOfDeletedObjects) {
getPM();
Query<?> query =
asSingleString
? queryElementHolder.getSingleStringQuery(pm)
: queryElementHolder.getAPIQuery(pm);
delete(
assertion,
query,
queryElementHolder.toString(),
queryElementHolder.getParamValues(),
expectedNrOfDeletedObjects);
boolean positive = expectedNrOfDeletedObjects >= 0;
if (positive) {
execute(
assertion,
queryElementHolder,
asSingleString,
queryElementHolder.isUnique() ? null : Collections.emptyList());
}
}
/**
* Calls {@link Query#deletePersistentAll()}, or
* {@link Query#deletePersistentAll(java.util.Map), or
* {@link Query#deletePersistentAll(java.lang.Object[])
* depending on the type of argument <code>parameters</code>.
* If the number of deleted objects does not
* match <code>expectedNrOfDeletedObjects</code>,
* then the test case fails prompting argument <code>assertion</code>.
* Argument <code>singleStringQuery</code> is only used as part
* of the failure message.
* @param assertion the assertion to prompt if the test case fails.
* @param query the query to execute.
* @param singleStringQuery the single string representation of the query.
* @param parameters the parmaters of the query.
* @param expectedNrOfDeletedObjects the expected number of deleted objects.
*/
@SuppressWarnings("unchecked")
private void delete(
String assertion,
Query<?> query,
String singleStringQuery,
Object parameters,
long expectedNrOfDeletedObjects) {
boolean positive = expectedNrOfDeletedObjects >= 0;
getPM();
Transaction tx = pm.currentTransaction();
tx.begin();
try {
try {
long nr;
if (parameters == null) {
nr = query.deletePersistentAll();
} else if (parameters instanceof Object[]) {
nr = query.deletePersistentAll((Object[]) parameters);
} else if (parameters instanceof Map) {
nr = query.deletePersistentAll((Map<String, Object>) parameters);
} else {
throw new IllegalArgumentException(
"Argument parameters " + "must be instance of Object[], Map, or null.");
}
if (logger.isDebugEnabled()) {
logger.debug(nr + " objects deleted.");
}
if (positive) {
if (nr != expectedNrOfDeletedObjects) {
fail(
assertion,
"deletePersistentAll returned "
+ nr
+ ", expected is "
+ expectedNrOfDeletedObjects
+ ". Query: "
+ singleStringQuery);
}
} else {
fail(assertion, "deletePersistentAll must throw JDOUserException: " + singleStringQuery);
}
} finally {
query.closeAll();
}
tx.commit();
} catch (JDOUserException e) {
if (positive) {
String msg = "JDOUserException thrown while executing query:\n" + singleStringQuery;
throw new JDOException(msg, e);
}
} catch (JDOException e) {
String msg = "JDOException thrown while executing query:\n" + singleStringQuery;
throw new JDOException(msg, e);
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
}
}