//
//  ERXEC.java
//  ERExtensions
//
//  Created by Max Muller on Sun Feb 23 2003.
//
package org.wojc.client;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.wojc.common.Utilities;

import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eocontrol.*;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSNotification;


// TODO document
public class JCEC extends EOEditingContext {
	
	/** general logging */
	public static final Logger log = Logger.getLogger(JCEC.class);

	/**
	 * logs a message when set to DEBUG, autoLocking is enabled and an EC is
	 * used without a lock.
	 */
	public static final Logger lockLogger = 
		Logger.getLogger("org.wojc.client.JCEC.LockLogger");

	/**
	 * logs a message with a stack trace when set to DEBUG and an EC is
	 * locked/unlocked.
	 */
	public static final Logger lockLoggerTrace = 
		Logger.getLogger("org.wojc.client.JCEC.LockLoggerTrace");

	/** Logs a message when set to DEBUG and an EC is locked/unlocked. */
	public static final Logger lockTrace = 
		Logger.getLogger("org.wojc.client.JCEC.LockTrace");
	
	static{
	//	BasicConfigurator.configure();
	//	Logger.getRootLogger().setLevel(Level.DEBUG);
	}
	
	/**
	 * if traceOpenEditingContextLocks is true, this contains the stack trace
	 * from this EC's call to lock
	 */
	private Exception creationTrace;
	private List<Exception> openLockTraces;
	/**
	 * if traceOpenEditingContextLocks is true, this will contain the name of
	 * the locking thread
	 */
	private String nameOfLockingThread;

	/** if true, there is an autolock on this editingContext */ 
	private boolean autoLocked;

	/** how many times has the EC been locked. */
	private int lockCount;

	/**
	 * holds a flag if the EC is in finalize(). This is needed because we can't
	 * autolock then.
	 */
	private boolean isFinalizing;

	/** holds a flag if editing context locks should be traced */
	private static Boolean traceOpenLocks;

	public static boolean traceOpenLocks() {
		return traceOpenLocks == null ? false : traceOpenLocks;
	}
	
	/**
	 * Sets whether or not open editing context lock tracing is enabled.
	 */
	public static void setTraceOpenLocks(boolean value) {
		traceOpenLocks = value;
	}

	private static ThreadLocal<List<EOEditingContext>> locks = 
		new ThreadLocal<List<EOEditingContext>>() {
		
		@Override
		protected List<EOEditingContext> initialValue() {
			return new ArrayList<EOEditingContext>();
		}
	};

	/**
	 * Pushes the given EC to the array of locked ECs in the current thread. The
	 * ECs left over after the RR-loop will be automagically unlocked.
	 * 
	 * @param ec locked EOEditingContext
	 */
	public static void pushLockedContextForCurrentThread(EOEditingContext ec) {
		if(ec != null) {
			List<EOEditingContext> ecs = locks.get();
			ecs.add(ec);
			if (log.isDebugEnabled()) {
				log.debug("After pushing: " + ecs);
			}
		}
	}

	/**
	 * Pops the given EC from the array of contexts to unlock. The ECs left over
	 * after the RR-loop will be automagically unlocked.
	 * 
	 * @param ec unlocked EOEditingContext
	 */
	public static void popLockedContextForCurrentThread(EOEditingContext ec) {
		if (ec != null) {
			List<EOEditingContext> ecs = locks.get();
			if (ecs != null) {
				int index = ecs.lastIndexOf(ec);
				if (index >= 0) {
					ecs.remove(index);
				}
				else {
					log.error("Should pop, but ec not found in List! " 
							+ Thread.currentThread().getName() + 
							", ec: " + ec + ", ecs:" + ecs);
				}
			}
			if (log.isDebugEnabled()) {
				log.debug("After popping: " + ecs);
			}
		}
	}

	/**
	 * Unlocks all remaining locked contexts in the current thread. You
	 * shouldn't call this yourself, but let the Unlocker handle it for you.
	 */
	public static void unlockAllContextsForCurrentThread() {
		List<EOEditingContext> ecs =  locks.get();
		if (ecs != null && ecs.size() > 0) {
			if (log.isDebugEnabled()) {
				log.debug("Unlock remaining: " + ecs);
			}
			// we can't use an iterator, because calling unlock() will remove
			// the EC from end of the list
			for (int i = ecs.size() - 1; i >= 0; i--) {
				EOEditingContext ec = (EOEditingContext) ecs.get(i);
				boolean openAutoLocks = (ec instanceof JCEC && ((JCEC) ec).isAutoLocked());
				if (openAutoLocks) {
					log.debug("Unlocking autolocked editing context: " + ec);
				}
				else {
					log.warn("Unlocking context that wasn't unlocked in RR-Loop!: " + ec);
				}
				try {
					ec.unlock();
				}
				catch (IllegalStateException ex) {
					log.error("Could not unlock EC: " + ec, ex);
				}
				
			}
		}
	}

	/** default constructor */
	public JCEC() {
		this(defaultParentObjectStore());
	}

	/** alternative constructor */
	public JCEC(EOObjectStore os) {
		super(os);
		if (traceOpenLocks()) {
			creationTrace = new Exception("Creation");
			creationTrace.fillInStackTrace();
		}
	}

	/** Utility to delete a bunch of objects. */
	public void deleteObjects(NSArray objects) {
		for (int i = objects.count(); i-- > 0;) {
			Object o = objects.objectAtIndex(i);
			if (o instanceof EOEnterpriseObject) {
				EOEnterpriseObject eo = (EOEnterpriseObject) o;
				if (eo.editingContext() != null) {
					eo.editingContext().deleteObject(eo);
				}
			}
		}
	}

	/** Returns the number of outstanding locks. */
	public int lockCount() {
		return lockCount;
	}

	/**
	 * If traceOpenEditingContextLocks is true, returns the stack trace from
	 * when this EC was created
	 */
	public Exception creationTrace() {
		return creationTrace;
	}

	/**
	 * If traceOpenEditingContextLocks is true, returns the stack trace from
	 * when this EC was locked
	 */
	public List<Exception> openLockTraces() {
		return openLockTraces;
	}

	/**
	 * Overridden to emmit log messages and push this instance to the locked
	 * editing contexts in this thread.
	 */
	public void lock() {
		if (traceOpenLocks()) {
			synchronized (this) {
				if (openLockTraces == null) {
					openLockTraces = new LinkedList<Exception>();
				}
				Exception openLockTrace = new Exception("Locked");
				openLockTrace.fillInStackTrace();
				String nameOfCurrentThread = Thread.currentThread().getName();
				if (openLockTraces.size() == 0) {
					openLockTraces.add(openLockTrace);
					nameOfLockingThread = nameOfCurrentThread;
					// NSLog.err.appendln("+++ Lock number (" +
					// _stackTraces.count() + ") in " + nameOfCurrentThread);
				}
				else {
					if (nameOfCurrentThread.equals(nameOfLockingThread)) {
						openLockTraces.add(openLockTrace);
						// NSLog.err.appendln("+++ Lock number (" +
						// _stackTraces.count() + ") in " +
						// nameOfCurrentThread);
					}
					else {
						StringBuffer buf = new StringBuffer(1024);
						buf.append(System.identityHashCode(this) + 
								" Attempting to lock editing context from " + 
								nameOfCurrentThread + " that was previously locked in " + 
								nameOfLockingThread + "\n");
						buf.append(" Current stack trace: " +
								Utilities.stackTrace(openLockTrace) + "\n");
						buf.append(" Lock count: " + openLockTraces.size() + "\n");
						for(Exception existingOpenLockTrace : openLockTraces){
							buf.append(" Existing lock: " + 
									Utilities.stackTrace(existingOpenLockTrace));
						}
						buf.append(" Created: " + Utilities.stackTrace(creationTrace));
						log.warn(buf);
					}
				}
			}
		}
		lockCount++;
		super.lock();
		if (!isAutoLocked() && lockLogger.isDebugEnabled()) {
			if (lockTrace.isDebugEnabled()) {
				lockLogger.debug("locked " + this, new Exception());
			}
			else {
				lockLogger.debug("locked " + this);
			}
		}
		pushLockedContextForCurrentThread(this);
	}

	/**
	 * Overridden to emit log messages and pull this instance from the locked
	 * editing contexts in this thread.
	 */
	public void unlock() {
		popLockedContextForCurrentThread(this);
		super.unlock();
		if (!isAutoLocked() && lockLogger.isDebugEnabled()) {
			if (lockTrace.isDebugEnabled()) {
				lockLogger.debug("unlocked " + this, new Exception());
			}
			else {
				lockLogger.debug("unlocked " + this);
			}
		}
		lockCount--;
		// If coalesceAutoLocks is true, then we will often end up with
		// a hanging autoLock at the final unlock, so we want to reset the
		// autolock count to zero so things behave properly when you
		// next use autolocking
		if (lockCount == 0 && autoLocked) {
			autoLocked = false;
		}
		if (traceOpenLocks()) {
			synchronized (this) {
				if (openLockTraces != null) {
					
					if (openLockTraces.size() > 0) {
						openLockTraces.remove(openLockTraces.size() - 1);
					}
					if (openLockTraces.size() == 0) {
						nameOfLockingThread = null;
						openLockTraces = null;
					}
				}
			}
		}
	}

	/**
	 * Utility to actually emit the log messages and do the locking, based on
	 * the result of {@link #useAutoLock()}.
	 * 
	 * @param method
	 *            method name which to prepend to log message
	 * @return whether we did lock automatically
	 */
	protected boolean autoLock(String method) {
		boolean wasAutoLocked = false;

		if (lockCount == 0) {
			wasAutoLocked = true;
			autoLocked = true;
			lock();
		}

		if (lockCount == 0 && !isAutoLocked() && !isFinalizing) {
			if (lockTrace.isDebugEnabled()) {
				lockTrace.debug("called method " + method + 
						" without a lock, ec=" + this, new Exception());
			}
			else {
				lockLogger.warn("called method " + method + 
						" without a lock, ec=" + this);
			}
		}

		return wasAutoLocked;
	}

	/**
	 * Utility to unlock the EC is it was locked in the previous invocation.
	 * 
	 * @param wasAutoLocked true if the EC was autolocked
	 */
	protected void autoUnlock(boolean wasAutoLocked) {
		if (wasAutoLocked) {
			if (autoLocked) {
				unlock();
				autoLocked = false;
			}
		}
	}

	/**
	 * Returns whether we did autolock this instance.
	 * 
	 * @return true if we were autolocked.
	 */
	public boolean isAutoLocked() {
		return autoLocked;
	}

	protected void _checkOpenLockTraces() {
		if (openLockTraces != null && openLockTraces.size() != 0) {
			log.error(System.identityHashCode(this) + 
					" Disposed with " + openLockTraces.size() + 
					" locks (finalizing = " + isFinalizing + ")");
			for(Exception existingOpenLockTrace : openLockTraces){
				log.error(System.identityHashCode(this) + 
						" Existing lock: ", existingOpenLockTrace);
			}
			log.error(System.identityHashCode(this) + " created: ", creationTrace);
		}
	}

	public void dispose() {
		if (traceOpenLocks()) {
			_checkOpenLockTraces();
		}
		super.dispose();
	}

	/** Overridden to support automatic autoLocking. */
	public void finalize() throws Throwable {
		isFinalizing = true;
		if (traceOpenLocks()) {
			_checkOpenLockTraces();
		}
		super.finalize();
	}

	/** Overridden to support automatic autoLocking. */
	public void reset() {
		boolean wasAutoLocked = autoLock("reset");
		try {
			super.reset();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void recordObject(EOEnterpriseObject eoenterpriseobject, EOGlobalID eoglobalid) {
		boolean wasAutoLocked = autoLock("recordObject");
		try {
			super.recordObject(eoenterpriseobject, eoglobalid);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void forgetObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("forgetObject");
		try {
			super.forgetObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void processRecentChanges() {
		boolean wasAutoLocked = autoLock("processRecentChanges");
		try {
			super.processRecentChanges();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray updatedObjects() {
		boolean wasAutoLocked = autoLock("updatedObjects");
		try {
			return super.updatedObjects();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray registeredObjects() {
		boolean wasAutoLocked = autoLock("registeredObjects");
		try {
			return super.registeredObjects();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray insertedObjects() {
		boolean wasAutoLocked = autoLock("insertedObjects");
		try {
			return super.insertedObjects();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray deletedObjects() {
		boolean wasAutoLocked = autoLock("deletedObjects");
		try {
			return super.deletedObjects();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void setSharedEditingContext(EOSharedEditingContext eosharededitingcontext) {
		boolean wasAutoLocked = autoLock("setSharedEditingContext");
		try {
			super.setSharedEditingContext(eosharededitingcontext);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public EOEnterpriseObject objectForGlobalID(EOGlobalID eoglobalid) {
		boolean wasAutoLocked = autoLock("objectForGlobalID");
		try {
			return super.objectForGlobalID(eoglobalid);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public EOGlobalID globalIDForObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("globalIDForObject");
		try {
			return super.globalIDForObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSDictionary committedSnapshotForObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("committedSnapshotForObject");
		try {
			return super.committedSnapshotForObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSDictionary currentEventSnapshotForObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("currentEventSnapshotForObject");
		try {
			return super.currentEventSnapshotForObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void objectWillChange(Object obj) {
		boolean wasAutoLocked = autoLock("objectWillChange");
		try {
			super.objectWillChange(obj);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void insertObjectWithGlobalID(EOEnterpriseObject eoenterpriseobject, 
			EOGlobalID eoglobalid) {
		boolean wasAutoLocked = autoLock("insertObjectWithGlobalID");
		try {
			super.insertObjectWithGlobalID(eoenterpriseobject, eoglobalid);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void insertObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("insertObject");
		try {
			super.insertObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/**
	 * Overridden to support autoLocking and to call mightDelete() on subclasses
	 * of ERXEnterpriseObject.
	 */
	public void deleteObject(EOEnterpriseObject eo) {
		boolean wasAutoLocked = autoLock("deleteObject");
		try {
			super.deleteObject(eo);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public boolean hasChanges() {
		boolean wasAutoLocked = autoLock("hasChanges");
		try {
			return super.hasChanges();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	protected void willSaveChanges(NSArray insertedObjects, 
			NSArray updatedObjects, NSArray deletedObjects) {
	}

	protected void didSaveChanges(NSArray insertedObjects, 
			NSArray updatedObjects, NSArray deletedObjects) {
	}

	/**
	 * Smarter version of normal <code>saveChanges()</code> method. Overridden
	 * to support autoLocking and a bugfix from Lenny Marks, calls up Will/Did
	 * methods on ERXEnterpriseObjects and corrects issues with
	 * <code>flushCaches()</code> needing to be called on objects in the
	 * parent context when committing the child context to the parent. If the
	 * editing context is a child of the object-store coordinator---that is,
	 * it's not a nested context---this method behaves exactly the same as
	 * <code>EOEditingContext.saveChanges()</code>. Otherwise, this method
	 * looks over the changed objects in <code>ec</code> (<code>updatedObjects()</code>,
	 * <code>insertedObjects()</code> and <code>deletedObjects()</code>).
	 * The changed objects lists are filtered for instances of
	 * <code>ERXGenericRecord</code>. The order of operations then becomes:
	 * 
	 * <ol>
	 * <li> Call <code>processRecentChanges()</code> on the child context to
	 * propagate changes.
	 * <li> Lock the parent editing context.
	 * <li> On the deleted objects list in the child editing context, call
	 * <code>flushCaches()</code> on each corresponding EO in the parent
	 * context.
	 * <li> Unlock the parent editing context.
	 * <li> Call <code>saveChanges()</code> on the child, committing the child
	 * changes to the parent editing context.
	 * <li> Lock the parent editing context.
	 * <li> On the objects that were updated or inserted in the child, call
	 * <code>flushCaches()</code> on each corresponding EO in the parent
	 * context.
	 * <li> Unlock the parent editing context.
	 * </ol>
	 * 
	 * <p>
	 * 
	 * The order of operations is a bit peculiar: flush deletes, save, flush
	 * inserts and updates. This is done because deletes must be flushed because
	 * there may be dependent computed state that needs to be reset. But
	 * following the delete being committed, the relationships to other objects
	 * cannot be relied upon so it isn't reliable to call flushCaches after the
	 * commit. It's not entirely correct to flush the deletes like this, but
	 * it's the best we can do.
	 * 
	 * <p>
	 * 
	 * This works around an issue in EOF that we don't get a merge notification
	 * when a child EC saves to its parent. Because there's no merge
	 * notification, <code>flushCaches()</code> isn't called by the EC
	 * delegate and we're essentially screwed vis-a-vis resetting computed
	 * state.
	 * 
	 * <p>
	 * 
	 * This method assumes that the <code>ec</code> is locked before this
	 * method is invoked, but this method will take the lock on the parent
	 * editing context if the <code>ec</code> is a nested context before and
	 * after the save in order to get the objects and to flush caches on them.
	 * 
	 */
	public void saveChanges() {
		boolean wasAutoLocked = autoLock("saveChanges");
        _EOAssertSafeMultiThreadedAccess("saveChanges()");
		savingChanges = true;
		try {
			NSArray insertedObjects = insertedObjects().immutableClone();
			NSArray updatedObjects = updatedObjects().immutableClone();
			NSArray deletedObjects = deletedObjects().immutableClone();

			willSaveChanges(insertedObjects, updatedObjects, deletedObjects);

			_saveChanges();

			didSaveChanges(insertedObjects, updatedObjects, deletedObjects);
		}finally {
			autoUnlock(wasAutoLocked);
			savingChanges = false;
		}

		processQueuedNotifications();
	}

	protected void _saveChanges() {
		super.saveChanges();
	}

	/** Overridden to support autoLocking. */
	public EOEnterpriseObject faultForGlobalID(
			EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("faultForGlobalID");
		try {
			return super.faultForGlobalID(eoglobalid, eoeditingcontext);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray arrayFaultWithSourceGlobalID(
			EOGlobalID eoglobalid, String s, EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("arrayFaultWithSourceGlobalID");
		try {
			return super.arrayFaultWithSourceGlobalID(eoglobalid, s, eoeditingcontext);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void initializeObject(EOEnterpriseObject eoenterpriseobject, 
			EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("initializeObject");
		try {
			super.initializeObject(eoenterpriseobject, eoglobalid, eoeditingcontext);
		}finally{
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void editingContextDidForgetObjectWithGlobalID(
			EOEditingContext eoeditingcontext, EOGlobalID eoglobalid) {
		boolean wasAutoLocked = autoLock("editingContextDidForgetObjectWithGlobalID");
		try {
			super.editingContextDidForgetObjectWithGlobalID(eoeditingcontext, eoglobalid);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray objectsForSourceGlobalID(EOGlobalID eoglobalid, String s, 
			EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("objectsForSourceGlobalID");
		try {
			return super.objectsForSourceGlobalID(eoglobalid, s, eoeditingcontext);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void refaultObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("refaultObject");
		try {
			super.refaultObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void refaultObject(EOEnterpriseObject eoenterpriseobject, 
			EOGlobalID eoglobalid, EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("refaultObject");
		try {
			super.refaultObject(eoenterpriseobject, eoglobalid, eoeditingcontext);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public NSArray objectsWithFetchSpecification(EOFetchSpecification eofetchspecification, 
			EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("objectsWithFetchSpecification");
		try {
			NSArray objects = super.objectsWithFetchSpecification(
					eofetchspecification, eoeditingcontext);
			return objects;
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void saveChangesInEditingContext(EOEditingContext eoeditingcontext) {
		boolean wasAutoLocked = autoLock("saveChangesInEditingContext");
		try {
			super.saveChangesInEditingContext(eoeditingcontext);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void refaultAllObjects() {
		boolean wasAutoLocked = autoLock("refaultAllObjects");
		try {
			super.refaultAllObjects();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void invalidateObjectsWithGlobalIDs(NSArray nsarray) {
		boolean wasAutoLocked = autoLock("invalidateObjectsWithGlobalIDs");
		try {
			super.invalidateObjectsWithGlobalIDs(nsarray);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void invalidateAllObjects() {
		boolean wasAutoLocked = autoLock("invalidateAllObjects");
		try {
			super.invalidateAllObjects();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void lockObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("lockObject");
		try {
			super.lockObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking and will/did revert delegate methods. * */
	public void revert() {
		boolean wasAutoLocked = autoLock("revert");
		try {
			super.revert();
		}finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	/** @deprecated */
	public void saveChanges(Object obj) {
		boolean wasAutoLocked = autoLock("saveChanges");
		try {
			saveChanges();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void refreshObject(EOEnterpriseObject eoenterpriseobject) {
		boolean wasAutoLocked = autoLock("refreshObject");
		try {
			super.refreshObject(eoenterpriseobject);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void undo() {
		boolean wasAutoLocked = autoLock("undo");
		try {
			super.undo();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	/** Overridden to support autoLocking. */
	public void redo() {
		boolean wasAutoLocked = autoLock("redo");
		try {
			super.redo();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}
	
/*	public void lockObjectStore(){
		boolean wasAutoLocked = autoLock("lockObjectStore");
		try {
			super.lockObjectStore();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}
	
	
	public void unlockObjectStore(){
		boolean wasAutoLocked = autoLock("unlockObjectStore");
		try {
			super.unlockObjectStore();
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}
*/
	/** Overridden to support autoLocking. */
	@SuppressWarnings("unchecked")
	public Object invokeRemoteMethod(EOEditingContext eoeditingcontext, 
			EOGlobalID eoglobalid, String s, Class aclass[], Object aobj[]) {
		boolean wasAutoLocked = autoLock("invokeRemoteMethod");
		try {
			return super.invokeRemoteMethod(eoeditingcontext, eoglobalid, s, aclass, aobj);
		}
		finally {
			autoUnlock(wasAutoLocked);
		}
	}

	private boolean savingChanges;
	private List<NSNotification> queuedNotifications = new LinkedList<NSNotification>();

	/**
	 * Overridden to add a bugfix from Lenny Marks
	 */
	public void _objectsChangedInStore(NSNotification nsnotification) {
		if (savingChanges)
			queuedNotifications.add(nsnotification);
		else
			super._objectsChangedInStore(nsnotification);
	}

	/**
	 * Overridden so add a bugfix from Lenny Marks
	 */
	private void processQueuedNotifications() {
		synchronized (queuedNotifications) {
			for(NSNotification n : queuedNotifications)
				_objectsChangedInStore(n);
			queuedNotifications.clear();
		}
	}

	/**
	 * Sets the delegate for this context.
	 */
	public void setDelegate(Object d) {
		if (log.isDebugEnabled()) {
			log.debug("setting delegate to " + d);
			log.debug(Utilities.stackTrace(new Exception()));
		}
		super.setDelegate(d);
	}
}
