blob: e8935c2bb20b04f9289c70d8f22a9907a51fbd85 [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
*
* 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 org.apache.jdo.tck;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.jdo.Extent;
import javax.jdo.JDOException;
import javax.jdo.JDOFatalException;
import javax.jdo.JDOHelper;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public abstract class JDO_Test extends TestCase {
public static final int TRANSIENT = 0;
public static final int PERSISTENT_NEW = 1;
public static final int PERSISTENT_CLEAN = 2;
public static final int PERSISTENT_DIRTY = 3;
public static final int HOLLOW = 4;
public static final int TRANSIENT_CLEAN = 5;
public static final int TRANSIENT_DIRTY = 6;
public static final int PERSISTENT_NEW_DELETED = 7;
public static final int PERSISTENT_DELETED = 8;
public static final int PERSISTENT_NONTRANSACTIONAL = 9;
public static final int PERSISTENT_NONTRANSACTIONAL_DIRTY = 10;
public static final int DETACHED_CLEAN = 11;
public static final int DETACHED_DIRTY = 12;
public static final int NUM_STATES = 13;
public static final int ILLEGAL_STATE = 13;
public static final String[] states = {
"transient",
"persistent-new",
"persistent-clean",
"persistent-dirty",
"hollow",
"transient-clean",
"transient-dirty",
"persistent-new-deleted",
"persistent-deleted",
"persistent-nontransactional",
"persistent-nontransactional-dirty",
"detached-clean",
"detached-dirty",
"illegal"
};
private static final int IS_PERSISTENT = 0;
private static final int IS_TRANSACTIONAL = 1;
private static final int IS_DIRTY = 2;
private static final int IS_NEW = 3;
private static final int IS_DELETED = 4;
private static final int IS_DETACHED = 5;
private static final int NUM_STATUSES = 6;
/*
* This table indicates the values returned by the status interrogation
* methods for each state. This is used to determine the current lifecycle
* state of an object.
*/
private static final boolean state_statuses[][] = {
// IS_PERSISTENT IS_TRANSACTIONAL IS_DIRTY IS_NEW IS_DELETED IS_DETACHED
// transient
{ false, false, false, false, false, false},
// persistent-new
{ true, true, true, true, false, false},
// persistent-clean
{ true, true, false, false, false, false},
// persistent-dirty
{ true, true, true, false, false, false},
// hollow
{ true, false, false, false, false, false},
// transient-clean
{ false, true, false, false, false, false},
// transient-dirty
{ false, true, true, false, false, false},
// persistent-new-deleted
{ true, true, true, true, true, false},
// persistent-deleted
{ true, true, true, false, true, false},
// persistent-nontransactional
{ true, false, false, false, false, false},
// persistent-nontransactional-dirty
{ true, false, true, false, false, false},
// detached_clean
{ false, false, false, false, false, true},
// detached_dirty
{ false, false, true, false, false, true}
};
/** Name of the PersistenceManagerFactoryClass PMF property. */
public static final String PMF_CLASS_PROP =
"javax.jdo.PersistenceManagerFactoryClass";
/** Name of the ConnectionURL PMF property. */
public static final String CONNECTION_URL_PROP =
"javax.jdo.option.ConnectionURL";
/** Name of the ConnectionUserName PMF property. */
public static final String CONNECTION_USERNAME_PROP =
"javax.jdo.option.ConnectionUserName";
/** Name of the ConnectionPassword PMF property. */
public static final String CONNECTION_PASSWORD_PROP =
"javax.jdo.option.ConnectionPassword";
/** identitytype value for applicationidentity. */
public static final String APPLICATION_IDENTITY = "applicationidentity";
/** identitytype value for datastoreidentity. */
public static final String DATASTORE_IDENTITY = "datastoreidentity";
/**
* String indicating the type of identity used for the current test case.
* The value is either "applicationidentity" or "datastoreidentity".
*/
protected final String identitytype = System.getProperty("jdo.tck.identitytype");
/**
* String indicating the name of the schema for the current test.
*/
protected final String schemaname = System.getProperty("jdo.tck.schemaname");
/** Name of the file containing the properties for the PMF. */
protected static String PMFProperties = System.getProperty("PMFProperties");
/** Flag indicating whether to clean up data after tests or not.
* If false then test will not clean up data from database.
* The default value is true.
*/
protected static boolean cleanupData =
System.getProperty("jdo.tck.cleanupaftertest", "true").equalsIgnoreCase("true");
/** Flag indicating whether to close the PMF after each test or not.
* It defaults to false.
*/
protected static final boolean closePMFAfterEachTest =
System.getProperty("jdo.tck.closePMFAfterEachTest", "false").equalsIgnoreCase("true");
/** The Properties object for the PersistenceManagerFactory. */
protected static Properties PMFPropertiesObject;
/** The PersistenceManagerFactory. */
protected static PersistenceManagerFactory pmf;
/** The collection of supported options of the pmf. */
private static Collection supportedOptions;
/** The name of the pmf supported options summary file. */
private static final String PMF_SUPPORTED_OPTIONS_FILE_NAME = "pmf_supported_options.txt";
/** The PersistenceManager. */
protected PersistenceManager pm;
// Flag indicating successful test run
protected boolean testSucceeded;
/** Logger */
protected Log logger =
LogFactory.getFactory().getInstance("org.apache.jdo.tck");
/** true if debug logging in enabled. */
protected boolean debug = logger.isDebugEnabled();
/**
* Indicates an exception thrown in method <code>tearDown</code>.
* At the end of method <code>tearDown</code> this field is nullified.
*/
private Throwable tearDownThrowable;
/**
* A list of registered oid instances.
* Corresponding pc instances are deleted in <code>localTearDown</code>.
*/
private Collection tearDownInstances = new LinkedList();
/**
* A list of registered pc classes.
* The extents of these classes are deleted in <code>localTearDown</code>.
*/
private Collection tearDownClasses = new LinkedList();
/** */
protected final void setUp() throws Exception {
pmf = getPMF();
localSetUp();
}
/**
* Subclasses may override this method to allocate any data and resources
* that they need in order to successfully execute this testcase.
*/
protected void localSetUp() {}
/**
* Runs the bare test sequence.
* @exception Throwable if any exception is thrown
*/
public final void runBare() throws Throwable {
try {
testSucceeded = false;
setUp();
runTest();
testSucceeded = true;
}
catch (AssertionFailedError e) {
if (logger.isInfoEnabled())
logger.info("Exception during setUp or runtest: ", e);
throw e;
}
catch (Throwable t) {
if (logger.isInfoEnabled())
logger.info("Exception during setUp or runtest: ", t);
throw t;
}
finally {
tearDown();
if (debug) {
logger.debug("Free memory: " + Runtime.getRuntime().freeMemory());
}
}
}
/**
* Sets field <code>tearDownThrowable</code> if it is <code>null</code>.
* Else, the given throwable is logged using fatal log level.
* @param throwable the throwable
*/
private void setTearDownThrowable(String context, Throwable throwable)
{
if (logger.isInfoEnabled())
logger.info("Exception during "+context+": ", throwable);
if (this.tearDownThrowable == null) {
this.tearDownThrowable = throwable;
}
}
/**
* This method clears data and resources allocated by testcases.
* It first closes the persistence manager of this testcase.
* Then it calls method <code>localTearDown</code>.
* Subclasses may override that method to clear any data and resources
* that they have allocated in method <code>localSetUp</code>.
* Finally, this method closes the persistence manager factory.<p>
*
* <b>Note:</b>These methods are called always, regardless of any exceptions.
* The first caught exception is kept in field <code>tearDownThrowable</code>.
* That exception is thrown as a nested exception of <code>JDOFatalException</code>
* if and only if the testcase executed successful.
* Otherwise that exception is logged using fatal log level.
* All other exceptions are logged using fatal log level, always.<p>
*
* <b>Note:</b>By default, the method tearDown does not close the pmf.
* This is done at the end of each configuration, unless the property
* jdo.tck.closePMFAfterEachTest is set to true.
*/
protected final void tearDown() {
try {
cleanupPM();
}
catch (Throwable t) {
setTearDownThrowable("cleanupPM", t);
}
if ((pmf == null || pmf.isClosed()) &&
(this.tearDownInstances.size() > 0 || this.tearDownClasses.size() > 0))
throw new JDOFatalException ("PMF must not be nullified or closed when tear down instances and /or classes have been added.");
if (pmf != null && pmf.isClosed())
pmf = null;
try {
if (cleanupData) {
localTearDown();
}
}
catch (Throwable t) {
setTearDownThrowable("localTearDown", t);
}
if (closePMFAfterEachTest) {
try {
closePMF();
}
catch (Throwable t) {
setTearDownThrowable("closePMF", t);
}
}
if (this.tearDownThrowable != null) {
Throwable t = this.tearDownThrowable;
this.tearDownThrowable = null;
if (testSucceeded) {
// runTest succeeded, but this method threw exception => error
throw new JDOFatalException("Exception during tearDown", t);
}
}
}
/**
* Deletes all registered pc instances and extents of all registered pc classes.
* Subclasses may override this method to clear any data and resources
* that they have allocated in method <code>localSetUp</code>.
*/
protected void localTearDown() {
deleteTearDownInstances();
deleteTearDownClasses();
}
protected void addTearDownObjectId(Object oid) {
// ensure that oid is not a PC instance
if (JDOHelper.getObjectId(oid) != null ||
JDOHelper.isTransactional(oid))
throw new IllegalArgumentException("oid");
this.tearDownInstances.add(oid);
}
protected void addTearDownInstance(Object pc) {
Object oid = JDOHelper.getObjectId(pc);
addTearDownObjectId(oid);
}
protected void addTearDownClass(Class pcClass) {
this.tearDownClasses.add(pcClass);
}
protected void addTearDownClass(Class[] pcClasses) {
if (pcClasses == null) return;
for (int i = 0; i < pcClasses.length; ++i) {
addTearDownClass(pcClasses[i]);
}
}
/**
* Deletes and removes tear down instances.
* If there are no tear down instances,
* the this method is a noop.
* Otherwise, tear down instances are deleted
* exactly in the order they have been added.
* Tear down instances are deleted in a separate transaction.
*/
protected void deleteTearDownInstances() {
if (this.tearDownInstances.size() > 0) {
getPM();
try {
this.pm.currentTransaction().begin();
for (Iterator i = this.tearDownInstances.iterator(); i.hasNext(); ) {
Object pc;
try {
pc = this.pm.getObjectById(i.next(), true);
}
catch (JDOObjectNotFoundException e) {
pc = null;
}
// we only delete those persistent instances
// which have not been deleted by tests already.
if (pc != null) {
this.pm.deletePersistent(pc);
}
}
this.pm.currentTransaction().commit();
}
finally {
this.tearDownInstances.clear();
cleanupPM();
}
}
}
/**
* Deletes and removes tear down classes.
* If there are no tear down classes,
* the this method is a noop.
* Otherwise, tear down classes are deleted
* exactly in the order they have been added.
* Tear down classes are deleted in a separate transaction.
* Deleting a tear down class means to delete the extent.
*/
protected void deleteTearDownClasses() {
if (this.tearDownClasses.size() > 0) {
getPM();
try {
this.pm.currentTransaction().begin();
for (Iterator i = this.tearDownClasses.iterator(); i.hasNext(); ) {
this.pm.deletePersistentAll(getAllObjects(this.pm, (Class)i.next()));
}
this.pm.currentTransaction().commit();
}
finally {
this.tearDownClasses.clear();
cleanupPM();
}
}
}
/** */
protected Collection getAllObjects(PersistenceManager pm, Class pcClass) {
Collection col = new Vector() ;
Query query = pm.newQuery();
query.setClass(pcClass);
Extent candidates = null;
try {
candidates = pm.getExtent(pcClass, false);
} catch (JDOException ex) {
if (debug) logger.debug("Exception thrown for getExtent of class " +
pcClass.getName());
return col;
}
query.setCandidates(candidates);
Object result = query.execute();
return (Collection)result;
}
/**
* Get the <code>PersistenceManagerFactory</code> instance
* for the implementation under test.
* @return field <code>pmf</code> if it is not <code>null</code>,
* else sets field <code>pmf</code> to a new instance and returns that instance.
*/
protected PersistenceManagerFactory getPMF()
{
if (pmf == null) {
PMFPropertiesObject = loadProperties(PMFProperties); // will exit here if no properties
pmf = JDOHelper.getPersistenceManagerFactory(PMFPropertiesObject);
if (supportedOptions == null) {
supportedOptions = pmf.supportedOptions();
}
}
return pmf;
}
/**
* Get the <code>PersistenceManagerFactory</code> instance
* for the implementation under test. This method does NOT use the
* JDOHelper method to retrieve the PMF, instead it creates an instance of
* the class specified as javax.jdo.PersistenceManagerFactoryClass
* property. The returned PMF is not configured.
* @return field <code>pmf</code> if it is not <code>null</code>,
* else sets field <code>pmf</code> to a new instance and returns that instance.
*/
protected PersistenceManagerFactory getUnconfiguredPMF()
{
if (pmf == null) {
PMFPropertiesObject = loadProperties(PMFProperties); // will exit here if no properties
String name = PMFPropertiesObject.getProperty(PMF_CLASS_PROP);
try {
Class pmfClass = Class.forName(name);
pmf = (PersistenceManagerFactory) pmfClass.newInstance();
if (supportedOptions == null) {
supportedOptions = pmf.supportedOptions();
}
} catch (ClassNotFoundException ex) {
throw new JDOException("Cannot find PMF class '" + name + "'.",
ex);
} catch (InstantiationException ex) {
throw new JDOException("Cannot instantiate PMF class '" +
name + "'.", ex);
} catch (IllegalAccessException ex) {
throw new JDOException("Cannot access PMF class '" + name +
"' or its no-arg constructor.", ex);
}
}
return pmf;
}
/**
* Get the <code>PersistenceManager</code> instance
* for the implementation under test.
*/
protected PersistenceManager getPM() {
if (pm == null) {
pm = getPMF().getPersistenceManager();
}
return pm;
}
/**
* This method cleans up the environment: closes the
* <code>PersistenceManager</code>. This should avoid leaving
* multiple PersistenceManager instances around, in case the
* PersistenceManagerFactory performs PersistenceManager pooling.
*/
protected void cleanupPM()
{
cleanupPM(pm);
pm = null;
}
/**
* This method cleans up the specified
* <code>PersistenceManager</code>. If the pm still has an open
* transaction, it will be rolled back, before closing the pm.
*/
protected static void cleanupPM(PersistenceManager pm)
{
if ((pm != null) && !pm.isClosed()) {
if (pm.currentTransaction().isActive()) {
pm.currentTransaction().rollback();
}
pm.close();
}
}
/** Closes the pmf stored in this instance. */
public static void closePMF() {
JDOException failure = null;
while (pmf != null) {
try {
if (!pmf.isClosed())
pmf.close();
pmf = null;
}
catch (JDOException ex) {
// store failure of first call pmf.close
if (failure == null)
failure = ex;
PersistenceManager[] pms = getFailedPersistenceManagers(
"closePMF", ex);
for (int i = 0; i < pms.length; i++) {
cleanupPM(pms[i]);
}
}
}
// rethrow JDOException thrown by pmf.close
if (failure != null)
throw failure;
}
/** */
protected static PersistenceManager[] getFailedPersistenceManagers(
String assertionFailure, JDOException ex) {
Throwable[] nesteds = ex.getNestedExceptions();
int numberOfExceptions = nesteds==null ? 0 : nesteds.length;
PersistenceManager[] result = new PersistenceManager[numberOfExceptions];
for (int i = 0; i < numberOfExceptions; ++i) {
JDOException exc = (JDOException)nesteds[i];
Object failedObject = exc.getFailedObject();
if (exc.getFailedObject() instanceof PersistenceManager) {
result[i] = (PersistenceManager)failedObject;
} else {
throw new JDOFatalException(assertionFailure,
"Unexpected failed object of type: " +
failedObject.getClass().getName());
}
}
return result;
}
/**
* This method load Properties from a given file.
*/
protected Properties loadProperties(String fileName)
{
if (fileName == null) {
fileName = System.getProperty("user.home") + "/.jdo/PMFProperties.properties";
}
Properties props = new Properties();
InputStream propStream = null;
try {
propStream = new FileInputStream(fileName);
}
catch (IOException ex) {
System.out.println("Could not open properties file \"" + fileName + "\"");
System.out.println("Please specify a system property PMFProperties " +
"with the PMF properties file name as value " +
"(defaults to {user.home}/.jdo/PMFProperties.properties)");
System.exit(1);
}
try {
props.load(propStream);
}
catch (IOException ex) {
System.out.println("Error loading properties file \"" + fileName + "\"");
ex.printStackTrace();
System.exit(1);
}
return props;
}
/**
* Prints the specified msg (if debug is true), before it aborts the
* test case.
*/
public void fail(String assertionFailure, String msg) {
if (debug) logger.debug(msg);
fail(assertionFailure + NL + msg);
}
// Helper methods to check for supported options
/**
* Dump the supportedOptions to the a file in the specified directory.
*/
public static void dumpSupportedOptions(String directory) {
if (supportedOptions == null)
return;
File file = new File(directory, PMF_SUPPORTED_OPTIONS_FILE_NAME);
if (file.exists())
// PMF supported options have been dumped before => return
return;
PrintStream resultStream = null;
try {
resultStream = new PrintStream(new FileOutputStream(file));
for (Iterator it = supportedOptions.iterator(); it.hasNext();) {
resultStream.println((String)it.next());
}
} catch (FileNotFoundException e) {
throw new JDOFatalException(
"dumpSupportedOptions: cannot create file " + file.getName(), e);
} finally {
if (resultStream != null)
resultStream.close();
}
}
/**
* Prints a message (if debug is true) saying the test with the
* specified name is not executed, because the JDO implementation under
* test does not support the specified optional feature.
* @param testName the name of the test method that is skipped.
* @param optionalFeature the name of the option not supported by the
* JDO implementation under tets.
*/
protected void printUnsupportedOptionalFeatureNotTested(
String testName, String optionalFeature) {
if (debug) {
logger.debug(
"Test " + testName +
" was not run, because optional feature " + optionalFeature +
" is not supported by the JDO implementation under test");
}
}
/** Reports whether TransientTransactional is supported. */
public boolean isTransientTransactionalSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.TransientTransactional");
}
/** Reports whether NontransactionalRead is supported. */
public boolean isNontransactionalReadSupported(){
return getPMF().supportedOptions().contains(
"javax.jdo.option.NontransactionalRead");
}
/** Reports whether NontransactionalWrite is supported. */
public boolean isNontransactionalWriteSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.NontransactionalWrite");
}
/** Reports whether RetainValues is supported. */
public boolean isRetainValuesSupported()
{
return getPMF().supportedOptions().contains(
"javax.jdo.option.RetainValues");
}
/** Reports whether Optimistic is supported. */
public boolean isOptimisticSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.Optimistic");
}
/** Reports whether Application Identity is supported. */
public boolean isApplicationIdentitySupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.ApplicationIdentity");
}
/** Reports whether Datastore Identity is supported. */
public boolean isDatastoreIdentitySupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.DatastoreIdentity");
}
/** Reports whether Non-Durable Identity is supported. */
public boolean isNonDurableIdentitySupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.NonDurableIdentity");
}
/** Reports whether an <code>ArrayList</code> collection is supported. */
public boolean isArrayListSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.ArrayList");
}
/** Reports whether a <code>HashMap</code> collection is supported. */
public boolean isHashMapSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.HashMap");
}
/** Reports whether a <code>Hashtable</code> collection is supported. */
public boolean isHashtableSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.Hashtable");
}
/** Reports whether a <code>LinkedList</code> collection is supported. */
public boolean isLinkedListSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.LinkedList");
}
/** Reports whether a <code>TreeMap</code> collection is supported. */
public boolean isTreeMapSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.TreeMap");
}
/** Reports whether a <code>TreeSet</code> collection is supported. */
public boolean isTreeSetSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.TreeSet");
}
/** Reports whether a <code>Vector</code> collection is supported. */
public boolean isVectorSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.Vector");
}
/** Reports whether a <code>Map</code> collection is supported. */
public boolean isMapSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.Map");
}
/** Reports whether a <code>List</code> collection is supported. */
public boolean isListSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.List");
}
/** Reports whether arrays are supported. */
public boolean isArraySupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.Array");
}
/** Reports whether a null collection is supported. */
public boolean isNullCollectionSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.NullCollection");
}
/** Reports whether Changing Application Identity is supported. */
public boolean isChangeApplicationIdentitySupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.ChangeApplicationIdentity");
}
/** Reports whether Binary Compatibility is supported. */
public boolean isBinaryCompatibilitySupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.BinaryCompatibility");
}
/** Reports whether UnconstrainedVariables is supported. */
public boolean isUnconstrainedVariablesSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.UnconstrainedVariables");
}
/** Reports whether SQL queries are supported. */
public boolean isSQLSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.query.SQL");
}
/** Reports whether getting the DataStoreConnection is supported. */
public boolean isDataStoreConnectionSupported() {
return getPMF().supportedOptions().contains(
"javax.jdo.option.GetDataStoreConnection");
}
/**
* Determine if a class is loadable in the current environment.
*/
public static boolean isClassLoadable(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException ex) {
return false;
}
}
/**
* Determine if the environment is 1.4 version of JRE or better.
*/
public static boolean isJRE14orBetter() {
return isClassLoadable("java.util.Currency");
}
/**
* This utility method returns a <code>String</code> that indicates the
* current state of an instance.
* @param o The object.
* @return The current state of the instance, by using the
* <code>JDOHelper</code> state interrogation methods.
*/
public static String getStateOfInstance(Object o)
{
boolean existingEntries = false;
StringBuffer buff = new StringBuffer("{");
if( JDOHelper.isPersistent(o) ){
buff.append("persistent");
existingEntries = true;
}
if( JDOHelper.isTransactional(o) ){
if( existingEntries ) buff.append(", ");
buff.append("transactional");
existingEntries = true;
}
if( JDOHelper.isDirty(o) ){
if( existingEntries ) buff.append(", ");
buff.append("dirty");
existingEntries = true;
}
if( JDOHelper.isNew(o) ){
if( existingEntries ) buff.append(", ");
buff.append("new");
existingEntries = true;
}
if( JDOHelper.isDeleted(o) ){
if( existingEntries ) buff.append(", ");
buff.append("deleted");
}
if( JDOHelper.isDetached(o) ){
if( existingEntries ) buff.append(", ");
buff.append("detached");
}
buff.append("}");
return buff.toString();
}
/**
* This method will return the current lifecycle state of an instance.
*/
public static int currentState(Object o)
{
boolean[] status = new boolean[NUM_STATUSES];
status[IS_PERSISTENT] = JDOHelper.isPersistent(o);
status[IS_TRANSACTIONAL] = JDOHelper.isTransactional(o);
status[IS_DIRTY] = JDOHelper.isDirty(o);
status[IS_NEW] = JDOHelper.isNew(o);
status[IS_DELETED] = JDOHelper.isDeleted(o);
status[IS_DETACHED] = JDOHelper.isDetached(o);
int i, j;
outerloop:
for( i = 0; i < NUM_STATES; ++i ){
for( j = 0; j < NUM_STATUSES; ++j ){
if( status[j] != state_statuses[i][j] )
continue outerloop;
}
return i;
}
return NUM_STATES;
}
/**
* Tests if a found state matches an expected state.
*/
public static boolean compareStates(int found_state, int expected_state)
{
// status interrogation gives same values for PERSISTENT_NONTRANSACTIONAL and HOLLOW
return (expected_state < 0
|| found_state == expected_state
|| (found_state == HOLLOW && expected_state == PERSISTENT_NONTRANSACTIONAL)
|| (found_state == PERSISTENT_NONTRANSACTIONAL && expected_state == HOLLOW));
}
/** This method mangles an object by changing all its non-static,
* non-final fields.
* It returns true if the object was mangled, and false if there
* are no fields to mangle.
*/
protected boolean mangleObject (Object oid)
throws Exception {
Field[] fields = getModifiableFields(oid);
if (fields.length == 0) return false;
for (int i = 0; i < fields.length; ++i) {
Field field = fields[i];
Class fieldType = field.getType();
if (fieldType == long.class) {
field.setLong(oid, 10000L + field.getLong(oid));
} else if (fieldType == int.class) {
field.setInt(oid, 10000 + field.getInt(oid));
} else if (fieldType == short.class) {
field.setShort(oid, (short)(10000 + field.getShort(oid)));
} else if (fieldType == byte.class) {
field.setByte(oid, (byte)(100 + field.getByte(oid)));
} else if (fieldType == char.class) {
field.setChar(oid, (char)(10 + field.getChar(oid)));
} else if (fieldType == String.class) {
field.set(oid, "This is certainly a challenge" + (String)field.get(oid));
} else if (fieldType == Integer.class) {
field.set(oid, new Integer(10000 + ((Integer)field.get(oid)).intValue()));
} else if (fieldType == Long.class) {
field.set(oid, new Long(10000L + ((Long)field.get(oid)).longValue()));
} else if (fieldType == Short.class) {
field.set(oid, new Short((short)(10000 + ((Short)field.get(oid)).shortValue())));
} else if (fieldType == Byte.class) {
field.set(oid, new Byte((byte)(100 + ((Byte)field.get(oid)).byteValue())));
} else if (fieldType == Character.class) {
field.set(oid, new Character((char)(10 + ((Character)(field.get(oid))).charValue())));
}
}
return true;
}
/** Returns modifiable Fields of the class of the parameter.
* Fields are considered modifiable if they are not static or final.
* This method requires several permissions in order to run with
* a SecurityManager, hence the doPrivileged block:
* <ul>
* <li>ReflectPermission("suppressAccessChecks")</li>
* <li>RuntimePermission("accessDeclaredMembers")</li>
* </ul>
*/
protected Field[] getModifiableFields(final Object obj) {
return (Field[])AccessController.doPrivileged(
new PrivilegedAction () {
public Object run () {
Class cls = obj.getClass();
List result = new ArrayList();
Field[] fields = cls.getFields();
for (int i = 0; i < fields.length; ++i) {
Field field = fields[i];
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) ||
Modifier.isStatic(modifiers))
continue;
field.setAccessible(true);
result.add(field);
}
return result.toArray(new Field[result.size()]);
}
}
);
}
/**
* Returns <code>true</code> if the current test runs with application
* identity. This means the system property jdo.tck.identitytype has the
* value applicationidentity.
* @return <code>true</code> if current test runs with application
* identity; <code>false</code> otherwise:
*/
public boolean runsWithApplicationIdentity() {
return APPLICATION_IDENTITY.equals(identitytype);
}
/**
* Prints a message (if debug is true) saying the test with the
* specified name is not executed, because the JDO implementation under
* test is run for an inapplicable identity type.
* @param testName the name of the test method that is skipped.
* @param requiredIdentityType the name of the required identity type.
*/
protected void printNonApplicableIdentityType(
String testName, String requiredIdentityType) {
if (debug) {
logger.debug(
"Test " + testName +
" was not run, because it is only applicable for identity type " +
requiredIdentityType +
". The identity type of the current configuration is " +
identitytype);
}
}
/**
* Returns the value of the PMF property
* given by argument <code>key</code>.
* @param key the key
* @return the value
*/
protected String getPMFProperty(String key) {
return PMFPropertiesObject.getProperty(key);
}
/**
* Returns <code>true</code> if the implementation under test
* supports all JDO options contained in system property
* <code>jdo.tck.requiredOptions</code>.
* @return <code>true</code> if the implementation under test
* supports all JDO options contained in system property
* <code>jdo.tck.requiredOptions</code>
*/
protected boolean isTestToBePerformed() {
boolean isTestToBePerformed = true;
String requiredOptions = System.getProperty("jdo.tck.requiredOptions");
Collection supportedOptions = getPMF().supportedOptions();
StringTokenizer tokenizer = new StringTokenizer(
requiredOptions, " ,;\n\r\t");
while (tokenizer.hasMoreTokens()) {
String requiredOption = tokenizer.nextToken();
logger.debug("Required option: " + requiredOption);
if (!requiredOption.equals("") &&
!supportedOptions.contains(requiredOption)) {
isTestToBePerformed = false;
printUnsupportedOptionalFeatureNotTested(
getClass().getName(), requiredOption);
}
}
return isTestToBePerformed;
}
/** New line.
*/
public static final String NL = System.getProperty("line.separator");
/** A buffer of of error messages.
*/
protected static StringBuffer messages;
/** Appends to error messages.
*/
protected static synchronized void appendMessage(String message) {
if (messages == null) {
messages = new StringBuffer(NL);
}
messages.append(message);
messages.append(NL);
}
/** Appends to error messages.
*/
protected static synchronized void deferredAssertTrue(boolean test,
String context, String message) {
if (!test) {
appendMessage(context + ": " +message);
}
}
/**
* Returns collected error messages, or <code>null</code> if there
* are none, and clears the buffer.
*/
protected static synchronized String retrieveMessages() {
if (messages == null) {
return null;
}
final String msg = messages.toString();
messages = null;
return msg;
}
/**
* Fail the test if there are any error messages.
*/
protected void failOnError() {
String errors = retrieveMessages();
if (errors != null) {
fail (errors);
}
}
}