/*
 * ====================================================================
 * 
 * The ObjectStyle Group Software License, Version 1.0
 * 
 * Copyright (c) 2006 The ObjectStyle Group and individual authors of the
 * software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any, must
 * include the following acknowlegement: "This product includes software
 * developed by the ObjectStyle Group (http://objectstyle.org/)." Alternately,
 * this acknowlegement may appear in the software itself, if and wherever such
 * third-party acknowlegements normally appear.
 * 
 * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse or
 * promote products derived from this software without prior written permission.
 * For written permission, please contact andrus@objectstyle.org.
 * 
 * 5. Products derived from this software may not be called "ObjectStyle" nor
 * may "ObjectStyle" appear in their names without prior written permission of
 * the ObjectStyle Group.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many individuals on
 * behalf of the ObjectStyle Group. For more information on the ObjectStyle
 * Group, please see <http://objectstyle.org/>.
 *  
 */
package org.objectstyle.wolips.eomodeler.core.model;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.objectstyle.woenvironment.plist.PropertyListParserException;
import org.objectstyle.woenvironment.plist.WOLPropertyListSerialization;
import org.objectstyle.wolips.baseforplugins.util.ComparisonUtils;
import org.objectstyle.wolips.baseforplugins.util.URLUtils;
import org.objectstyle.wolips.eomodeler.core.model.history.EOEntityAddedEvent;
import org.objectstyle.wolips.eomodeler.core.model.history.EOEntityDeletedEvent;
import org.objectstyle.wolips.eomodeler.core.model.history.ModelEvents;
import org.objectstyle.wolips.eomodeler.core.utils.NamingConvention;

public class EOModel extends UserInfoableEOModelObject<EOModelGroup> implements ISortableEOModelObject {
	public static final String CURRENT_VERSION = "1.0.1";

	public static final String DIRTY = "dirty";

	public static final String ENTITY = "entity";

	public static final String ADAPTOR_NAME = "adaptorName";

	public static final String VERSION = "version";

	public static final String NAME = "name";

	public static final String ENTITIES = "entities";

	public static final String STORED_PROCEDURE = "storedProcedure";

	public static final String STORED_PROCEDURES = "storedProcedures";

	public static final String ACTIVE_DATABASE_CONFIG = "activeDatabaseConfig";

	public static final String DATABASE_CONFIG = "databaseConfig";

	public static final String DATABASE_CONFIGS = "databaseConfigs";

	public static final String ATTRIBUTE_NAMING_CONVENTION = "attributeNamingConvention";

	public static final String ENTITY_NAMING_CONVENTION = "entityNamingConvention";

	public static final String MODEL_SAVING = "modelSaving";

	public static final String REVERSE_ENGINEERED = "reverseEngineered";

	private EOModelGroup myModelGroup;

	private String myName;

	private String myVersion;

	private Set<EOEntity> myEntities;

	private Set<EODatabaseConfig> myDatabaseConfigs;

	private EODatabaseConfig myActiveDatabaseConfig;

	private Set<EOStoredProcedure> myStoredProcedures;

	private Set<String> myDeletedEntityNamesInObjectStore;

	private Set<String> myDeletedEntityNames;

	private Set<String> myDeletedStoredProcedureNames;

	private EOModelMap myModelMap;

	private boolean myDirty;

	private URL myModelURL;

	private Set<EOAttribute> myPrototypeAttributeCache;

	private ModelEvents _modelEvents;

	private NamingConvention _entityNamingConvention;

	private NamingConvention _attributeNamingConvention;
	
	private boolean _reverseEngineered;
	
	private EOLastModified _lastModified;

	public EOModel(String _name) {
		myName = _name;
		myEntities = new HashSet<EOEntity>();
		myStoredProcedures = new HashSet<EOStoredProcedure>();
		myDatabaseConfigs = new HashSet<EODatabaseConfig>();
		myDeletedEntityNamesInObjectStore = new PropertyListSet<String>();
		myDeletedEntityNames = new PropertyListSet<String>();
		myDeletedStoredProcedureNames = new PropertyListSet<String>();
		myVersion = "2.1";
		myModelMap = new EOModelMap();
		_modelEvents = new ModelEvents();
		_entityNamingConvention = NamingConvention.DEFAULT;
		_attributeNamingConvention = NamingConvention.DEFAULT;
	}

	public EOModel(URL modelURL) throws EOModelException, IOException {
		this(EOModelGroup.getModelNameForURL(modelURL));
		Set<EOModelVerificationFailure> failures = new HashSet<EOModelVerificationFailure>();
		loadFromURL(modelURL, failures);
		if (failures.size() > 0) {
			throw new EOModelException("Failed to load model from URL '" + modelURL + "': " + failures);
		}
	}

	public EOModel(URL modelURL, Set<EOModelVerificationFailure> failures) throws EOModelException, IOException {
		this(EOModelGroup.getModelNameForURL(modelURL));
		loadFromURL(modelURL, failures);
	}

	protected void _storedProcedureChanged(EOStoredProcedure _storedProcedure, String _propertyName, Object _oldValue, Object _newValue) {
		firePropertyChange(EOModel.STORED_PROCEDURE + "." + _propertyName, _oldValue, _newValue);
		firePropertyChange(EOModel.STORED_PROCEDURE, null, _storedProcedure);
	}

	public void _setModelGroup(EOModelGroup _modelGroup) {
		myModelGroup = _modelGroup;
	}

	public void setEntityNamingConvention(NamingConvention entityNamingConvention) {
		NamingConvention oldEntityNamingConvention = _entityNamingConvention;
		_entityNamingConvention = entityNamingConvention;
		firePropertyChange(EOModel.ENTITY_NAMING_CONVENTION, oldEntityNamingConvention, _entityNamingConvention);
	}

	public NamingConvention getEntityNamingConvention() {
		return _entityNamingConvention;
	}

	public void setAttributeNamingConvention(NamingConvention attributeNamingConvention) {
		NamingConvention oldAttributeNamingConvention = _attributeNamingConvention;
		_attributeNamingConvention = attributeNamingConvention;
		firePropertyChange(EOModel.ATTRIBUTE_NAMING_CONVENTION, oldAttributeNamingConvention, _attributeNamingConvention);
	}

	public NamingConvention getAttributeNamingConvention() {
		return _attributeNamingConvention;
	}

	public Set<EOModelReferenceFailure> getReferenceFailures() {
		return new HashSet<EOModelReferenceFailure>();
	}

	public String guessPackageName() {
		return guessPackageName(getEntities());
	}

	public String guessPackageName(Set<EOEntity> _entities) {
		String guessPackageName = null;
		for (EOEntity entity : _entities) {
			String className = entity.getClassName();
			if (className != null) {
				int packageNameEnd = className.lastIndexOf('.');
				String packageName;
				if (packageNameEnd != -1) {
					packageName = className.substring(0, packageNameEnd);
				} else {
					packageName = "";
				}
				// ignore blank package names ...
				if (!"".equals(packageName)) {
					if (guessPackageName == null) {
						guessPackageName = packageName;
					} else if ("".equals(guessPackageName)) {
						// it can't change from ""
					} else if (!guessPackageName.equals(packageName)) {
						guessPackageName = "";
//						if (guessPackageName.startsWith(packageName)) {
//							guessPackageName = packageName;
//						} else if (packageName.startsWith(guessPackageName)) {
//							// leave it as is
//						} else {
//							int lastMatchingIndex = -1;
//							for (int index = 0; index < guessPackageName.length() && index < packageName.length(); index++) {
//								if (guessPackageName.charAt(index) == packageName.charAt(index)) {
//									lastMatchingIndex = index;
//								} else {
//									break;
//								}
//							}
//							if (lastMatchingIndex != -1) {
//								guessPackageName = guessPackageName.substring(0, lastMatchingIndex);
//								if (guessPackageName.endsWith(".")) {
//									guessPackageName = guessPackageName.substring(0, guessPackageName.length() - 1);
//								}
//							} else {
//								guessPackageName = "";
//							}
//						}
					}
				}
			}
		}
		return guessPackageName;
	}

	public EOEntity addBlankEntity(String name) throws DuplicateNameException {
		String newEntityName = findUnusedEntityName(name);
		EOEntity entity = new EOEntity(newEntityName);
		entity.guessExternalNameInModel(this);
		entity.guessClassNameInModel(this);
		addEntity(entity);

		if (myModelGroup != null && myModelGroup.hasProjectWonder()) {
			EOAttribute pk = new EOAttribute("id");
			pk.setPrototype(getPrototypeAttributeNamed("id"));
			pk.setColumnName("id");
			pk.setPrimaryKey(Boolean.TRUE);
			pk.setClassProperty(Boolean.FALSE);
			pk.setUsedForLocking(Boolean.TRUE);
			entity.addAttribute(pk);
		}

		return entity;
	}

	public boolean isEditing() {
		EOModelGroup modelGroup = getModelGroup();
		boolean editing = (modelGroup == null || getName().equals(modelGroup.getEditingModelName()));
		return editing;
	}

	public boolean isReverseEngineered() {
		return _reverseEngineered;
	}

	public void setReverseEngineered(boolean reverseEngineered) {
		Boolean oldReverseEngineered = Boolean.valueOf(_reverseEngineered);
		_reverseEngineered = reverseEngineered;
		firePropertyChange(EOModel.REVERSE_ENGINEERED, oldReverseEngineered, Boolean.valueOf(_reverseEngineered));
	}

	public boolean isDirty() {
		return myDirty;
	}

	public void setDirty(boolean _dirty) {
		Boolean oldDirty = Boolean.valueOf(myDirty);
		myDirty = _dirty;
		firePropertyChange(EOModel.DIRTY, oldDirty, Boolean.valueOf(myDirty));
	}

	protected void firePropertyChange(String _propertyName, Object _oldValue, Object _newValue) {
		super.firePropertyChange(_propertyName, _oldValue, _newValue);
	}

	protected void _propertyChanged(String _propertyName, Object _oldValue, Object _newValue) {
		if (!myDirty && !EOModel.DIRTY.equals(_propertyName) && !EOModel.MODEL_SAVING.equals(_propertyName)) {
			setDirty(true);
		}
		if (myModelGroup != null) {
			myModelGroup._modelChanged(this, _propertyName, _oldValue, _newValue);
		}
	}

	protected void _entityChanged(EOEntity _entity, String _propertyName, Object _oldValue, Object _newValue) {
		firePropertyChange(EOModel.ENTITY + "." + _propertyName, _oldValue, _newValue);
		firePropertyChange(EOModel.ENTITY, null, _entity);
	}

	protected void _databaseConfigChanged(EODatabaseConfig _databaseConfig, String _propertyName, Object _oldValue, Object _newValue) {
		firePropertyChange(EOModel.DATABASE_CONFIG + "." + _propertyName, _oldValue, _newValue);
		firePropertyChange(EOModel.DATABASE_CONFIG, null, _databaseConfig);
		if (_databaseConfig == myActiveDatabaseConfig) {
			clearCachedPrototypes(null, false);
		}
	}

	public EODatabaseConfig getDatabaseConfigNamed(String _name) {
		EODatabaseConfig matchingDatabaseConfig = null;
		Iterator<EODatabaseConfig> databaseConfigsIter = myDatabaseConfigs.iterator();
		while (matchingDatabaseConfig == null && databaseConfigsIter.hasNext()) {
			EODatabaseConfig entity = databaseConfigsIter.next();
			if (ComparisonUtils.equals(entity.getName(), _name)) {
				matchingDatabaseConfig = entity;
			}
		}
		return matchingDatabaseConfig;
	}

	public String findUnusedDatabaseConfigName(String _newName) {
		return _findUnusedName(_newName, "getDatabaseConfigNamed");
	}

	public void _checkForDuplicateDatabaseConfigName(EODatabaseConfig _databaseConfig, String _newName, Set<EOModelVerificationFailure> _failures) throws DuplicateDatabaseConfigNameException {
		EODatabaseConfig existingDatabaseConfig = getDatabaseConfigNamed(_newName);
		if (existingDatabaseConfig != null && existingDatabaseConfig != _databaseConfig) {
			if (_failures == null) {
				throw new DuplicateDatabaseConfigNameException(_newName, this);
			}
			String unusedName = findUnusedDatabaseConfigName(_newName);
			existingDatabaseConfig.setName(unusedName, true);
			_failures.add(new DuplicateDatabaseConfigFailure(this, _newName, unusedName));
		}
	}

	public EODatabaseConfig addBlankDatabaseConfig(String _name) throws DuplicateNameException {
		String newDatabaseConfigNameBase = _name;
		String newDatabaseConfigName = findUnusedDatabaseConfigName(newDatabaseConfigNameBase);
		EODatabaseConfig databaseConfig = new EODatabaseConfig(newDatabaseConfigName);
		addDatabaseConfig(databaseConfig);
		return databaseConfig;
	}
	
	public boolean canConvertDatabaseConfigsToSingleConnectionDictionary() {
		boolean canConvertDatabaseConfigsToSingleConnectionDictionary;
		if (myDatabaseConfigs == null || myDatabaseConfigs.size() == 0) {
			canConvertDatabaseConfigsToSingleConnectionDictionary = true;
		}
		else if (myDatabaseConfigs.size() == 1) {
			EODatabaseConfig databaseConfig = getDatabaseConfigs().iterator().next();
			canConvertDatabaseConfigsToSingleConnectionDictionary = databaseConfig.canConvertToConnectionDictionary();
		}
		else {
			canConvertDatabaseConfigsToSingleConnectionDictionary = false;
		}
		return canConvertDatabaseConfigsToSingleConnectionDictionary;
	}

	public void addDatabaseConfig(EODatabaseConfig _databaseConfig) throws DuplicateNameException {
		addDatabaseConfig(_databaseConfig, true, null);
	}

	public void addDatabaseConfig(EODatabaseConfig _databaseConfig, boolean _fireEvents, Set<EOModelVerificationFailure> _failures) throws DuplicateNameException {
		_databaseConfig._setModel(this);
		_checkForDuplicateDatabaseConfigName(_databaseConfig, _databaseConfig.getName(), _failures);
		_databaseConfig.pasted();
		if (_fireEvents) {
			Set<EODatabaseConfig> oldDatabaseConfigs = null;
			oldDatabaseConfigs = myDatabaseConfigs;
			Set<EODatabaseConfig> newEntities = new HashSet<EODatabaseConfig>();
			newEntities.addAll(myDatabaseConfigs);
			newEntities.add(_databaseConfig);
			myDatabaseConfigs = newEntities;
			firePropertyChange(EOModel.DATABASE_CONFIGS, oldDatabaseConfigs, myDatabaseConfigs);
			if (myActiveDatabaseConfig == null) {
				setActiveDatabaseConfig(_databaseConfig);
			}
		} else {
			myDatabaseConfigs.add(_databaseConfig);
		}
	}

	public void removeDatabaseConfig(EODatabaseConfig _databaseConfig) {
		if (ComparisonUtils.equals(myActiveDatabaseConfig, _databaseConfig)) {
			EODatabaseConfig newActiveDatabaseConfig = null;
			if (!myDatabaseConfigs.isEmpty()) {
				Iterator<EODatabaseConfig> databaseConfigsIter = myDatabaseConfigs.iterator();
				while (newActiveDatabaseConfig == null && databaseConfigsIter.hasNext()) {
					EODatabaseConfig otherDatabaseConfig = databaseConfigsIter.next();
					if (otherDatabaseConfig != _databaseConfig) {
						newActiveDatabaseConfig = otherDatabaseConfig;
					}
				}
			}
			setActiveDatabaseConfig(newActiveDatabaseConfig);
		}
		Set<EODatabaseConfig> oldDatabaseConfigs = myDatabaseConfigs;
		Set<EODatabaseConfig> newDatabaseConfigs = new HashSet<EODatabaseConfig>();
		newDatabaseConfigs.addAll(myDatabaseConfigs);
		newDatabaseConfigs.remove(_databaseConfig);
		myDatabaseConfigs = newDatabaseConfigs;
		firePropertyChange(EOModel.DATABASE_CONFIGS, oldDatabaseConfigs, myDatabaseConfigs);
		_databaseConfig._setModel(null);
	}

	public void setActiveDatabaseConfig(EODatabaseConfig activeDatabaseConfig) {
		setActiveDatabaseConfig(activeDatabaseConfig, true);
	}

	public void setActiveDatabaseConfig(EODatabaseConfig activeDatabaseConfig, boolean fireEvents) {
		EODatabaseConfig oldActiveDatabaseConfig = myActiveDatabaseConfig;
		myActiveDatabaseConfig = activeDatabaseConfig;
		clearCachedPrototypes(null, false);
		if (fireEvents) {
			firePropertyChange(EOModel.ACTIVE_DATABASE_CONFIG, oldActiveDatabaseConfig, myActiveDatabaseConfig);
		}
	}

	public EODatabaseConfig getActiveDatabaseConfig() {
		return myActiveDatabaseConfig;
	}

	public EODatabaseConfig _createDatabaseConfig(String adaptorName, Map<Object, Object> connectionDictionary) {
		EODatabaseConfig defaultDatabaseConfig = new EODatabaseConfig(findUnusedDatabaseConfigName("Default"));
		defaultDatabaseConfig.setAdaptorName(adaptorName);
		if (connectionDictionary != null) {
			defaultDatabaseConfig.setConnectionDictionary(new HashMap<Object, Object>(connectionDictionary));
		}
		defaultDatabaseConfig.setPrototype(_getPreferredPrototypeEntity(adaptorName, connectionDictionary));
		defaultDatabaseConfig._setModel(this);
		return defaultDatabaseConfig;
	}

	public Map<EOAttribute, Set<EORelationship>> _createReferencingRelationshipsCache() {
		Map<EOAttribute, Set<EORelationship>> cache = new HashMap<EOAttribute, Set<EORelationship>>();
		_createReferencingRelationshipsCache(cache);
		return cache;
	}

	public void _createReferencingRelationshipsCache(Map<EOAttribute, Set<EORelationship>> cache) {
		for (EOEntity entity : getEntities()) {
			for (EORelationship relationship : entity.getRelationships()) {
				Set<EOAttribute> relatedAttributes = relationship.getRelatedAttributes();
				for (EOAttribute attribute : relatedAttributes) {
					Set<EORelationship> relatedRelationships = cache.get(attribute);
					if (relatedRelationships == null) {
						relatedRelationships = new HashSet<EORelationship>();
						cache.put(attribute, relatedRelationships);
					}
					relatedRelationships.add(relationship);
				}
			}
		}
	}

	public Map<EOEntity, Set<EOEntity>> _createInheritanceCache() {
		Map<EOEntity, Set<EOEntity>> cache = new HashMap<EOEntity, Set<EOEntity>>();
		_createInheritanceCache(cache);
		return cache;
	}

	public void _createInheritanceCache(Map<EOEntity, Set<EOEntity>> cache) {
		for (EOEntity entity : getEntities()) {
			EOEntity parentEntity = entity.getParent();
			if (parentEntity != null) {
				Set<EOEntity> childrenEntities = cache.get(parentEntity);
				if (childrenEntities == null) {
					childrenEntities = new HashSet<EOEntity>();
					cache.put(parentEntity, childrenEntities);
				}
				childrenEntities.add(entity);
			}
		}
	}

	public Set<EODatabaseConfig> getDatabaseConfigs() {
		return myDatabaseConfigs;
	}

	public Set<EODatabaseConfig> getSortedDatabaseConfigs() {
		return new PropertyListSet<EODatabaseConfig>(myDatabaseConfigs);
	}
	
//	public int hashCode() {
//		return myName.hashCode();
//	}
//
//	public boolean equals(Object _obj) {
//		return (_obj instanceof EOModel && ((EOModel) _obj).myName.equals(myName));
//	}

	public URL getModelURL() {
		return myModelURL;
	}

	public void setModelURL(URL _modelURL) {
		myModelURL = _modelURL;
	}

	public EOModelGroup getModelGroup() {
		return myModelGroup;
	}

	public String getVersion() {
		return myVersion;
	}

	public void setVersion(String _version) {
		String oldVersion = myVersion;
		myVersion = _version;
		firePropertyChange(EOModel.VERSION, _version, oldVersion);
	}

	public void setName(String _name) {
		String oldName = myName;
		myName = _name;
		firePropertyChange(EOModel.NAME, _name, oldName);
	}

	public String getName() {
		return myName;
	}

	/**
	 * Returns the list of entities that are not prototypes.
	 * 
	 * @return list of non prototype entities
	 */
	public Set<EOEntity> getNonPrototypeEntities() {
		Set<EOEntity> entities = new HashSet<EOEntity>();
		for (EOEntity entity : getEntities()) {
			if (!entity.isPrototype()) {
				entities.add(entity);
			}
		}
		return entities;
	}

	public Set<EOEntity> getEntities() {
		return myEntities;
	}

	public Set<EOEntity> getSortedEntities() {
		return new PropertyListSet<EOEntity>(myEntities);
	}

	public EOEntity _getEntityNamedAndCheckModelGroup(String newName) {
		EOEntity entity = myModelGroup.getEntityNamed(newName);
		if (entity == null) {
			entity = getEntityNamed(newName);
		}
		return entity;
	}

	public String findUnusedEntityName(String _newName) {
		return _findUnusedName(_newName, "_getEntityNamedAndCheckModelGroup");
	}

	public void _checkForDuplicateEntityName(EOEntity _entity, String _newName, Set<EOModelVerificationFailure> _failures) throws DuplicateEntityNameException {
		EOEntity existingEntity = null;
		if (myModelGroup != null) {
			existingEntity = myModelGroup.getEntityNamed(_newName);
		}
		// MS: We do this because at load time, the model thinks it's part of
		// the model group, but the model group doesnt'
		// think it contains the model, so we check both
		if (existingEntity == null) {
			existingEntity = getEntityNamed(_newName);
		}
		if (existingEntity != null && existingEntity != _entity) {
			// MS: For most duplicates, we can rename the original. But for
			// entities, they can be
			// in a totally separate model.
			if (_failures == null || _entity.getModel() != existingEntity.getModel()) {
				throw new DuplicateEntityNameException(_newName, this, existingEntity);
			}
			String unusedName = findUnusedEntityName(_newName);
			existingEntity.setName(unusedName, true);
			_failures.add(new DuplicateEntityFailure(this, _newName, unusedName));
		}
	}

	public void _entityNameChanged(String _originalName, String _oldName, String _newName) {
		if (myDeletedEntityNamesInObjectStore == null) {
			myDeletedEntityNamesInObjectStore = new PropertyListSet<String>();
		}
		if (_originalName != null && _originalName.length() > 0) {
			myDeletedEntityNamesInObjectStore.add(_originalName);
			// myDeletedEntityNamesInObjectStore.remove(_newName);
			myDeletedEntityNames.add(_originalName);
			// myDeletedEntityNames.remove(_newName);
			synchronized (_entitiesCache) {
				EOEntity entity = _entitiesCache.remove(_oldName);
				if (entity != null) {
					_entitiesCache.put(_newName, entity);
				}
			}
		}
	}

	public boolean containsEntityNamed(String _entityName) {
		return getEntityNamed(_entityName) != null;
	}

	public Set importEntitiesFromModel(URL sourceModelURL, Set<EOModelVerificationFailure> failures) throws EOModelException, IOException {
		EOModelGroup sourceModelGroup = new EOModelGroup();
		EOModel sourceModel = new EOModel("Temp");
		sourceModelGroup.addModel(sourceModel);
		sourceModel.loadFromURL(sourceModelURL, failures);
		sourceModel.resolve(failures);
		sourceModel.resolveFlattened(failures);
		return importEntitiesFromModel(sourceModel, failures);
	}

	public Set<EOEntity> importEntitiesFromModel(EOModel sourceModel, Set<EOModelVerificationFailure> failures) throws DuplicateNameException {
		Set<EOEntity> clonedEntities = new HashSet<EOEntity>();
		for (EOEntity entity : sourceModel.getEntities()) {
			clonedEntities.add(entity._cloneModelObject());
		}
		addEntities(clonedEntities, failures);
		guessPrototypes(clonedEntities);
		return clonedEntities;
	}

	public void guessPrototypes() {
		guessPrototypes(getEntities());
	}

	public void guessPrototypes(Set<EOEntity> entities) {
		for (EOEntity entity : entities) {
			// clonedEntity.setName(StringUtils.toUppercaseFirstLetter(clonedEntity.getName().toLowerCase()));
			for (EOAttribute attribute : entity.getAttributes()) {
				attribute.guessPrototype(true);
				// clonedAttribute.setName(clonedAttribute.getName().toLowerCase());
			}
			// Iterator clonedRelationshipsIter =
			// clonedEntity.getRelationships().iterator();
			// while (clonedRelationshipsIter.hasNext()) {
			// EORelationship clonedRelationship = (EORelationship)
			// clonedRelationshipsIter.next();
			// clonedRelationship.setName(clonedRelationship.getName().toLowerCase());
			// }
		}
	}

	private Map<String, EOEntity> _entitiesCache = new HashMap<String, EOEntity>();
	
	public void addEntities(Set<EOEntity> entities, Set<EOModelVerificationFailure> failures) throws DuplicateNameException {
		Set<EOEntity> oldEntities = new HashSet<EOEntity>();
		oldEntities.addAll(myEntities);

		for (EOEntity entity : entities) {
			addEntity(entity, false, false, failures);
		}
		for (EOEntity entity : entities) {
			entity.pasted();
		}

		firePropertyChange(EOModel.ENTITIES, oldEntities, myEntities);
	}

	public void addEntity(EOEntity _entity) throws DuplicateNameException {
		addEntity(_entity, true, true, null);
	}

	public void addEntity(EOEntity entity, boolean pasteImmediately, boolean fireEvents, Set<EOModelVerificationFailure> failures) throws DuplicateNameException {

		entity._setModel(this);
		_checkForDuplicateEntityName(entity, entity.getName(), failures);
		if (pasteImmediately) {
			entity.pasted();
		}
		synchronized (_entitiesCache) {
			_entitiesCache.put(entity.getName(), entity);
		}
		myDeletedEntityNames.remove(entity.getName());
		if (fireEvents) {
			Set<EOEntity> oldEntities = null;
			oldEntities = myEntities;
			Set<EOEntity> newEntities = new HashSet<EOEntity>();
			newEntities.addAll(myEntities);
			newEntities.add(entity);
			myEntities = newEntities;
			_modelEvents.addEvent(new EOEntityAddedEvent(entity));
			firePropertyChange(EOModel.ENTITIES, oldEntities, myEntities);
		} else {
			myEntities.add(entity);
		}
	}

	public void removeEntity(EOEntity _entity) {
		myDeletedEntityNames.add(_entity.getName());
		synchronized (_entitiesCache) {
			_entitiesCache.remove(_entity.getName());
		}
		Set<EOEntity> oldEntities = myEntities;
		Set<EOEntity> newEntities = new HashSet<EOEntity>();
		newEntities.addAll(myEntities);
		newEntities.remove(_entity);
		myEntities = newEntities;
		_modelEvents.addEvent(new EOEntityDeletedEvent(_entity));
		firePropertyChange(EOModel.ENTITIES, oldEntities, myEntities);
		_entity._setModel(null);
	}

	public EOEntity getEntityNamed(String name) {
		synchronized (_entitiesCache) {
			return _entitiesCache.get(name);
		}
	}

	public void _storedProcedureNameChanged(String _oldName, String _newName) {
		myDeletedStoredProcedureNames.add(_oldName);
		myDeletedStoredProcedureNames.remove(_newName);
	}

	public EOStoredProcedure addBlankStoredProcedure(String _name) throws DuplicateStoredProcedureNameException {
		String newStoredProcedureNameBase = _name;
		String newStoredProcedureName = findUnusedStoredProcedureName(newStoredProcedureNameBase);
		EOStoredProcedure storedProcedure = new EOStoredProcedure(newStoredProcedureName);
		addStoredProcedure(storedProcedure);
		return storedProcedure;
	}

	public void addStoredProcedure(EOStoredProcedure _storedProcedure) throws DuplicateStoredProcedureNameException {
		addStoredProcedure(_storedProcedure, true, null);
	}

	public void addStoredProcedure(EOStoredProcedure _storedProcedure, boolean _fireEvents, Set<EOModelVerificationFailure> _failures) throws DuplicateStoredProcedureNameException {
		_storedProcedure._setModel(this);
		_checkForDuplicateStoredProcedureName(_storedProcedure, _storedProcedure.getName(), _failures);
		_storedProcedure.pasted();
		myDeletedStoredProcedureNames.remove(_storedProcedure.getName());
		if (_fireEvents) {
			Set<EOStoredProcedure> oldStoredProcedures = myStoredProcedures;
			Set<EOStoredProcedure> newStoredProcedures = new HashSet<EOStoredProcedure>();
			newStoredProcedures.addAll(myStoredProcedures);
			newStoredProcedures.add(_storedProcedure);
			myStoredProcedures = newStoredProcedures;
			firePropertyChange(EOModel.STORED_PROCEDURES, oldStoredProcedures, myStoredProcedures);
		} else {
			myStoredProcedures.add(_storedProcedure);
		}
	}

	public void removeStoredProcedure(EOStoredProcedure _storedProcedure) {
		myDeletedStoredProcedureNames.add(_storedProcedure.getName());
		Set<EOStoredProcedure> oldStoredProcedures = myStoredProcedures;
		Set<EOStoredProcedure> newStoredProcedures = new HashSet<EOStoredProcedure>();
		newStoredProcedures.addAll(myStoredProcedures);
		newStoredProcedures.remove(_storedProcedure);
		myStoredProcedures = newStoredProcedures;
		firePropertyChange(EOModel.STORED_PROCEDURES, oldStoredProcedures, myStoredProcedures);
		_storedProcedure._setModel(null);
	}

	public String findUnusedStoredProcedureName(String _newName) {
		return _findUnusedName(_newName, "getStoredProcedureNamed");
	}

	public Set<EOStoredProcedure> getStoredProcedures() {
		return myStoredProcedures;
	}

	public Set<EOStoredProcedure> getSortedStoredProcedures() {
		return new PropertyListSet<EOStoredProcedure>(myStoredProcedures);
	}

	public EOStoredProcedure getStoredProcedureNamed(String _name) {
		EOStoredProcedure matchingStoredProcedure = null;
		Iterator<EOStoredProcedure> storedProceduresIter = myStoredProcedures.iterator();
		while (matchingStoredProcedure == null && storedProceduresIter.hasNext()) {
			EOStoredProcedure attribute = storedProceduresIter.next();
			if (ComparisonUtils.equals(attribute.getName(), _name)) {
				matchingStoredProcedure = attribute;
			}
		}
		return matchingStoredProcedure;
	}

	public void _checkForDuplicateStoredProcedureName(EOStoredProcedure _storedProcedure, String _newName, Set<EOModelVerificationFailure> _failures) throws DuplicateStoredProcedureNameException {
		EOStoredProcedure existingStoredProcedure = getStoredProcedureNamed(_newName);
		if (existingStoredProcedure != null && existingStoredProcedure != _storedProcedure) {
			if (_failures == null) {
				throw new DuplicateStoredProcedureNameException(_newName, this);
			}

			String unusedName = findUnusedStoredProcedureName(_newName);
			existingStoredProcedure.setName(unusedName, true);
			_failures.add(new DuplicateStoredProcedureFailure(this, _newName, unusedName));
		}
	}

	public URL getIndexURL() throws MalformedURLException {
		URL indexURL = null;
		if (myModelURL != null) {
			indexURL = new URL(myModelURL, "index.eomodeld");
		}
		return indexURL;
	}

	public void checkLastModified(Set<EOLastModified> lastModified) {
		if (_lastModified != null && _lastModified.hasBeenModified()) {
			lastModified.add(_lastModified);
		}
		for (EOEntity entity : getEntities()) {
			entity.checkLastModified(lastModified);
		}
		for (EOStoredProcedure storedProc : getStoredProcedures()) {
			storedProc.checkLastModified(lastModified);
		}
	}

	public void loadFromURL(URL _modelFolder, Set<EOModelVerificationFailure> _failures) throws EOModelException, MalformedURLException {
		// System.out.println("EOModel.loadFromURL: " + _modelFolder);
		URL indexURL = new URL(_modelFolder, "index.eomodeld");
		// if (!indexURL.exists()) {
		// throw new EOModelException(indexURL + " does not exist.");
		// }
		myModelURL = _modelFolder;
		Map rawModelMap;
		try {
			rawModelMap = (Map) WOLPropertyListSerialization.propertyListFromURL(indexURL, new EOModelParserDataStructureFactory());
		} catch (Exception e) {
			throw new EOModelException("index.eomodeld is corrupted.", e);
		}
		if (rawModelMap == null) {
			throw new EOModelException("index.eomodeld is corrupted.");
		}
		EOModelMap modelMap = new EOModelMap(rawModelMap);
		myModelMap = modelMap;
		Object version = modelMap.get("EOModelVersion");
		if (version instanceof String) {
			myVersion = (String) version;
		} else if (version instanceof Number) {
			myVersion = String.valueOf(((Number) version).floatValue());
		} else {
			throw new IllegalArgumentException("Unknown version format:" + version);
		}
		loadUserInfo(modelMap);

		Set<Map> entities = modelMap.getSet("entities");
		if (entities != null) {
			for (Map entitiesMap : entities) {
				EOModelMap entityMap = new EOModelMap(entitiesMap);
				String entityName = entityMap.getString("name", true);
				EOEntity entity = new EOEntity();
				URL entityURL = new URL(_modelFolder, entityName + ".plist");
				if (URLUtils.exists(entityURL)) {
					entity.loadFromURL(entityURL, _failures);
					URL fspecURL = new URL(_modelFolder, entityName + ".fspec");
					if (URLUtils.exists(fspecURL)) {
						entity.loadFetchSpecsFromURL(fspecURL, _failures);
					}
					if (entity.getName() == null) {
						_failures.add(new EOModelVerificationFailure(this, this, "The entity file " + entityURL + " defines an entity with no name.", false));
					}
					addEntity(entity, true, false, _failures);
				} else {
					_failures.add(new EOModelVerificationFailure(this, this, "The entity file " + entityURL + " was missing.", false));
				}
			}
		}

		Set<String> storedProcedureNames = modelMap.getSet("storedProcedures");
		if (storedProcedureNames != null) {
			for (String storedProcedureName : storedProcedureNames) {
				EOStoredProcedure storedProcedure = new EOStoredProcedure();
				URL storedProcedureURL = new URL(_modelFolder, storedProcedureName + ".storedProcedure");
				if (URLUtils.exists(storedProcedureURL)) {
					storedProcedure.loadFromURL(storedProcedureURL, _failures);
					addStoredProcedure(storedProcedure, false, _failures);
				} else {
					_failures.add(new EOModelVerificationFailure(this, this, "The stored procedure file " + storedProcedureURL + " was missing.", false));
				}
			}
		}

		Map<Object, Object> internalInfoMap = modelMap.getMap("internalInfo");
		if (internalInfoMap != null) {
			Set<String> deletedEntityNamesInObjectStore = modelMap.getSet("_deletedEntityNamesInObjectStore", true);
			if (deletedEntityNamesInObjectStore != null) {
				myDeletedEntityNamesInObjectStore = deletedEntityNamesInObjectStore;
			}
		}

		EOModelMap entityModelerMap = getEntityModelerMap(false);
		_entityNamingConvention = NamingConvention.loadFromMap("entityNamingConvention", entityModelerMap);
		_attributeNamingConvention = NamingConvention.loadFromMap("attributeNamingConvention", entityModelerMap);
		_reverseEngineered = entityModelerMap.getBoolean("reverseEngineered", false);

		Map<String, Map> databaseConfigs = entityModelerMap.getMap("databaseConfigs");
		if (databaseConfigs != null) {
			for (Map.Entry<String, Map> databaseConfigEntry : databaseConfigs.entrySet()) {
				String name = databaseConfigEntry.getKey();
				EODatabaseConfig databaseConfig = new EODatabaseConfig(name);
				databaseConfig.loadFromMap(new EOModelMap(databaseConfigEntry.getValue()), _failures);
				addDatabaseConfig(databaseConfig, false, _failures);
			}
		}
		EODatabaseConfig activeDatabaseConfig = null;
		String activeDatabaseConfigName = entityModelerMap.getString("activeDatabaseConfigName", false);
		if (activeDatabaseConfigName != null) {
			activeDatabaseConfig = getDatabaseConfigNamed(activeDatabaseConfigName);
		}

		String adaptorName = modelMap.getString("adaptorName", true);

		// If there is a connection dictionary, then look for a database config
		// that is equivalent ...
		Map<Object, Object> connectionDictionary = modelMap.getMap("connectionDictionary", true);
		if (connectionDictionary != null && !connectionDictionary.isEmpty()) {
			EODatabaseConfig tempConnectionDictionaryDatabaseConfig = _createDatabaseConfig(adaptorName, connectionDictionary);
			EODatabaseConfig connectionDictionaryDatabaseConfig = null;
			Iterator<EODatabaseConfig> databaseConfigsIter = myDatabaseConfigs.iterator();
			while (connectionDictionaryDatabaseConfig == null && databaseConfigsIter.hasNext()) {
				EODatabaseConfig databaseConfig = databaseConfigsIter.next();
				if (tempConnectionDictionaryDatabaseConfig.isEquivalent(databaseConfig, false)) {
					connectionDictionaryDatabaseConfig = databaseConfig;
				}
			}
			
			// if one isn't found, then make a new database config based off the
			// connection dictionary
			if (connectionDictionaryDatabaseConfig == null && canSave()) {
				connectionDictionaryDatabaseConfig = tempConnectionDictionaryDatabaseConfig;
				addDatabaseConfig(connectionDictionaryDatabaseConfig, false, _failures);
				//_failures.add(new EOModelVerificationFailure(this, this, "Creating default database config for model '" + getName() + "'.", true, null));
			}
			
			// if the identified active database config isn't the connection
			// dictionary config, then change the active config to the connection dictionary config
			if (activeDatabaseConfig != connectionDictionaryDatabaseConfig) {
				activeDatabaseConfig = connectionDictionaryDatabaseConfig;
			}
		}
		
		// If after all this, we still have no database configs, and you have an empty connection dictionary,
		// then make a new blank database config ... If you have a NULL connection dictionary, then we're
		// going to assume you did that on purpose.
		if (myDatabaseConfigs.isEmpty() && canSave() && connectionDictionary != null) {
			// If you didn't specify an adaptor name, let's just make it JDBC 
			if (adaptorName == null) {
				adaptorName = EODatabaseConfig.JDBC_ADAPTOR_NAME;
			}
			addDatabaseConfig(_createDatabaseConfig(adaptorName, null), false, _failures);
		}
		
		// try to always have at least one active database config
		if (activeDatabaseConfig == null && !myDatabaseConfigs.isEmpty()) {
			activeDatabaseConfig = myDatabaseConfigs.iterator().next();
		}
		if (activeDatabaseConfig != null) {
			if (ComparisonUtils.equals(activeDatabaseConfig.getName(), activeDatabaseConfigName)) {
				myActiveDatabaseConfig = activeDatabaseConfig;
			} else {
				setActiveDatabaseConfig(activeDatabaseConfig, false);
			}
		}

		_lastModified = new EOLastModified(indexURL);
	}

	public EOModelMap toMap() {
		EOModelMap modelMap = myModelMap.cloneModelMap();
		modelMap.setString("EOModelVersion", myVersion, true);
		String adaptorName = EODatabaseConfig.JDBC_ADAPTOR_NAME;
		Map connectionDictionary = null;
		if (myActiveDatabaseConfig != null) {
			connectionDictionary = myActiveDatabaseConfig.getConnectionDictionary();
			adaptorName = myActiveDatabaseConfig.getAdaptorName();
		}
		modelMap.put("connectionDictionary", connectionDictionary);
		modelMap.setString("adaptorName", adaptorName, true);

		Set<Map> entities = new PropertyListSet<Map>(EOModelMap.asArray(myModelMap.get("entities")));
		Set<String> entitiesWithSharedObjects = new PropertyListSet<String>(EOModelMap.asArray(myModelMap.get("entitiesWithSharedObjects")));
		for (EOEntity entity : myEntities) {
			EOModelMap entityMap = new EOModelMap();
			entityMap.setString("className", entity.getClassName(), true);
			EOEntity parent = entity.getParent();
			String parentName = (parent == null) ? null : parent.getName();
			entityMap.setString("parent", parentName, true);
			entityMap.setString("name", entity.getName(), true);
			entities.add(entityMap);
			if (entity.hasSharedObjects()) {
				entitiesWithSharedObjects.add(entity.getName());
			}
		}
		modelMap.setSet("entities", entities, true);

		modelMap.setSet("entitiesWithSharedObjects", entitiesWithSharedObjects, true);

		Map<Object, Object> internalInfoMap = modelMap.getMap("internalInfo");
		if (internalInfoMap == null) {
			internalInfoMap = new HashMap<Object, Object>();
		}
		if (myDeletedEntityNamesInObjectStore != null && !myDeletedEntityNamesInObjectStore.isEmpty()) {
			internalInfoMap.put("_deletedEntityNamesInObjectStore", myDeletedEntityNamesInObjectStore);
		} else {
			internalInfoMap.remove("_deletedEntityNamesInObjectStore");
		}
		modelMap.setMap("internalInfo", internalInfoMap, true);

		Set<String> storedProcedures = new PropertyListSet<String>(EOModelMap.asArray(myModelMap.get("storedProcedures")));
		for (EOStoredProcedure storedProcedure : myStoredProcedures) {
			storedProcedures.add(storedProcedure.getName());
		}
		modelMap.setSet("storedProcedures", storedProcedures, true);

		EOModelMap entityModelerMap = getEntityModelerMap(true);
		boolean canConvertDatabaseConfigsToSingleConnectionDictionary = canConvertDatabaseConfigsToSingleConnectionDictionary();
		if (canConvertDatabaseConfigsToSingleConnectionDictionary) {
			entityModelerMap.remove("databaseConfigs");
			entityModelerMap.remove("activeDatabaseConfigName");
		}
		else {
			Map<String, Map> databaseConfigs = new PropertyListMap<String, Map>();
			for (EODatabaseConfig databaseConfig : myDatabaseConfigs) {
				databaseConfigs.put(databaseConfig.getName(), databaseConfig.toMap());
			}
			entityModelerMap.setMap("databaseConfigs", databaseConfigs, true);

			if (myActiveDatabaseConfig == null || databaseConfigs.get(myActiveDatabaseConfig.getName()) == null) {
				entityModelerMap.remove("activeDatabaseConfigName");
			} else {
				entityModelerMap.put("activeDatabaseConfigName", myActiveDatabaseConfig.getName());
			}
		}
		
		NamingConvention.toMap(_entityNamingConvention, "entityNamingConvention", entityModelerMap);
		NamingConvention.toMap(_attributeNamingConvention, "attributeNamingConvention", entityModelerMap);
		
		entityModelerMap.setBoolean("reverseEngineered", Boolean.valueOf(_reverseEngineered), EOModelMap.YNOptionalDefaultNo);

		writeUserInfo(modelMap);

		return modelMap;
	}

	/**
	 * Returns the .eomodeld for the given folder. If this is an .eomodeld
	 * folder, it returns the folder you pass in. Otherwise, it creates a child
	 * with this model's name.
	 * 
	 * @param folder
	 *            the folder to write to
	 * @return the corresponding .eomodeld folder
	 */
	public File _eomodeldFolderForFolder(File folder) {
		File modelFolder;
		if (folder.getName().endsWith(".eomodeld")) {
			modelFolder = folder;
		} else {
			modelFolder = new File(folder, myName + ".eomodeld");
		}
		return modelFolder;
	}

	/**
	 * Returns true if you are able to write this model to the folder it was
	 * loaded from.
	 * 
	 * @return true if you are able to save this model
	 * @throws MalformedURLException
	 *             if the index URL is invalid
	 */
	public boolean canSave() throws MalformedURLException {
		boolean canSave;
		URL indexURL = getIndexURL();
		if (indexURL == null || indexURL.toExternalForm().startsWith("jar:")) {
			canSave = false;
		} else {
			File indexFile = URLUtils.cheatAndTurnIntoFile(indexURL);
			File modelFolder = indexFile.getParentFile();
			canSave = canSaveToFolder(modelFolder);
		}
		return canSave;
	}

	/**
	 * Returns true if you are able to write this model to the given folder.
	 * 
	 * @param parentFolder
	 *            the folder to write the model into
	 * @return true if you are able to write to the folder
	 */
	public boolean canSaveToFolder(File parentFolder) {
		boolean canSave;
		File modelFolder = _eomodeldFolderForFolder(parentFolder);
		if (modelFolder.exists()) {
			if (modelFolder.isFile()) {
				canSave = false;
			} else {
				canSave = modelFolder.canWrite();
			}
		} else {
			canSave = modelFolder.getParentFile().canWrite();
		}
		return canSave;
	}

	/**
	 * Saves this model to the same folder it was loaded from.
	 * 
	 * @return the .eomodeld folder
	 * @throws IOException
	 *             if the model could not be saved
	 * @throws PropertyListParserException
	 */
	public File save() throws IOException, PropertyListParserException {
		URL indexURL = getIndexURL();
		if (indexURL == null) {
			throw new IOException("This model has no index file URL.");
		}
		File indexFile = URLUtils.cheatAndTurnIntoFile(indexURL);
		File modelFolder = indexFile.getParentFile();
		return saveToFolder(modelFolder.getParentFile());
	}

	public File saveToFolder(File parentFolder) throws IOException, PropertyListParserException {
		firePropertyChange(EOModel.MODEL_SAVING, Boolean.FALSE, Boolean.TRUE);
		try {
			File modelFolder = _eomodeldFolderForFolder(parentFolder);
			if (!modelFolder.exists()) {
				if (!modelFolder.mkdirs()) {
					throw new IOException("Failed to create folder '" + modelFolder + "'.");
				}
			}
			myModelURL = modelFolder.toURL();
			File indexFile = new File(modelFolder, "index.eomodeld");
			EOModelMap modelMap = toMap();
			WOLPropertyListSerialization.propertyListToFile("Entity Modeler v" + EOModel.CURRENT_VERSION, indexFile, modelMap);
	
			if (myDeletedEntityNames != null) {
				for (String entityName : myDeletedEntityNames) {
					if (getEntityNamed(entityName) == null) {
						File entityFile = new File(modelFolder, entityName + ".plist");
						if (entityFile.exists()) {
							entityFile.delete();
						}
						File fspecFile = new File(modelFolder, entityName + ".fspec");
						if (fspecFile.exists()) {
							fspecFile.delete();
						}
					}
				}
			}
	
			for (EOEntity entity : myEntities) {
				if (entity.isEntityDirty()) {
					String entityName = entity.getName();
					File entityFile = new File(modelFolder, entityName + ".plist");
					File fspecFile = new File(modelFolder, entityName + ".fspec");
					entity.saveToFile(entityFile, fspecFile);
				}
			}
	
			if (myDeletedStoredProcedureNames != null) {
				for (String storedProcedureName : myDeletedStoredProcedureNames) {
					if (getStoredProcedureNamed(storedProcedureName) == null) {
						File storedProcedureFile = new File(modelFolder, storedProcedureName + ".storedProcedure");
						if (storedProcedureFile.exists()) {
							storedProcedureFile.delete();
						}
					}
				}
			}
	
			for (EOStoredProcedure storedProcedure : myStoredProcedures) {
				if (storedProcedure.isStoredProcedureDirty()) {
					String storedProcedureName = storedProcedure.getName();
					File storedProcedureFile = new File(modelFolder, storedProcedureName + ".storedProcedure");
					storedProcedure.saveToFile(storedProcedureFile);
				}
			}
	
			setDirty(false);
			_lastModified = new EOLastModified(indexFile);
	
			return modelFolder;
		}
		finally {
			firePropertyChange(EOModel.MODEL_SAVING, Boolean.TRUE, Boolean.FALSE);
		}
	}

	public void resolve(Set<EOModelVerificationFailure> _failures) {
		for (EOEntity entity : myEntities) {
			entity.resolve(_failures);
		}
	}

	public void resolveFlattened(Set<EOModelVerificationFailure> _failures) {
		for (EOEntity entity : myEntities) {
			entity.resolveFlattened(_failures);
		}
	}

	public void verify(Set<EOModelVerificationFailure> _failures) {
		VerificationContext verificationContext;
		EOModelGroup modelGroup = getModelGroup();
		if (modelGroup == null) {
			verificationContext = new VerificationContext(this);
		} else {
			verificationContext = new VerificationContext(modelGroup);
		}
		verify(_failures, verificationContext);
	}

	public void verify(Set<EOModelVerificationFailure> _failures, VerificationContext verificationContext) {
		// TODO

		for (EOEntity entity : myEntities) {
			entity.verify(_failures, verificationContext);
		}
	}

	public String getFullyQualifiedName() {
		return myName == null ? "Unknown Model '" + getModelURL() + "'" : myName;
	}

	@Override
	public EOModel _cloneModelObject() {
		throw new IllegalStateException("Not implemented");
	}

	@Override
	public Class<EOModelGroup> _getModelParentType() {
		return EOModelGroup.class;
	}

	@Override
	public EOModelGroup _getModelParent() {
		return getModelGroup();
	}

	@Override
	public void _addToModelParent(EOModelGroup modelParent, boolean findUniqueName, Set<EOModelVerificationFailure> failures) throws EOModelException {
		if (findUniqueName) {
			throw new IllegalArgumentException("Unable to unique model names.");
		}
		modelParent.addModel(this, failures);
	}

	@Override
	public void _removeFromModelParent(Set<EOModelVerificationFailure> failures) {
		getModelGroup().removeModel(this, failures);
	}

	public ModelEvents getModelEvents() {
		return _modelEvents;
	}

	public String toString() {
		return "[EOModel: name = " + myName + "; entities = " + myEntities + "]";
	}

	/** Begin Prototypes * */
	public synchronized void clearCachedPrototypes(Set<EOModelVerificationFailure> _failures, boolean _reload) {
		myPrototypeAttributeCache = null;
		for (EOEntity entity : myEntities) {
			entity.clearCachedPrototypes(_failures, _reload);
		}
	}

	public synchronized Set<String> getPrototypeAttributeNames() {
		Set<String> prototypeAttributeNames = new PropertyListSet<String>();
		for (EOAttribute attribute : getPrototypeAttributes()) {
			prototypeAttributeNames.add(attribute.getName());
		}
		return prototypeAttributeNames;
	}

	public String _getDefaultPrototypeEntityName(String prefix, String name) {
		return "EO" + (prefix != null ? prefix : "") + (name != null ? name : "") + "Prototypes";
	}

	public String getAdaptorName() {
		String adaptorName = EODatabaseConfig.JDBC_ADAPTOR_NAME;
		if (myActiveDatabaseConfig != null) {
			adaptorName = myActiveDatabaseConfig.getAdaptorName();
		}
		return adaptorName;
	}

	public String _getAdaptorPrototypeEntityName(String prefix, String adaptorName, String name) {
		String adaptorPrototypeEntityName = null;
		if (adaptorName != null) {
			adaptorPrototypeEntityName = "EO" + adaptorName + (prefix != null ? prefix : "") + (name != null ? name : "") + "Prototypes";
		}
		return adaptorPrototypeEntityName;
	}

	public String _getDriverPrototypeEntityName(String prefix, Map _connectionDictionary, String name) {
		String driverPrototypeEntityName = null;
		String adaptorName = getAdaptorName();
		// MS: Hardcoded JDBC reference hack ...
		if ("JDBC".equals(adaptorName)) {
			if (_connectionDictionary != null) {
				String pluginName = (String) _connectionDictionary.get("plugin");
				if (pluginName == null || pluginName.length() == 0) {
					String jdbcUrl = (String) _connectionDictionary.get("URL");
					if (jdbcUrl != null && jdbcUrl.length() > 0) {
						int firstColon = jdbcUrl.indexOf(':');
						int secondColon = jdbcUrl.indexOf(':', firstColon + 1);
						if (firstColon != -1 && secondColon != -1) {
							pluginName = jdbcUrl.substring(firstColon + 1, secondColon);
						}
					}
				}
				if (pluginName != null) {
					driverPrototypeEntityName = "EOJDBC" + pluginName + (prefix != null ? prefix : "") + (name != null ? name : "") + "Prototypes";
				}
			}
		}
		return driverPrototypeEntityName;
	}

	public EOEntity _getPreferredPrototypeEntity(String adaptorName, Map connectionDictionary) {
		EOEntity preferredPrototypEntity = _getPreferredPrototypeEntity("Custom", adaptorName, null, connectionDictionary);
		if (preferredPrototypEntity == null) {
			preferredPrototypEntity = _getPreferredPrototypeEntity(null, adaptorName, getName(), connectionDictionary);
			if (preferredPrototypEntity == null) {
				preferredPrototypEntity = _getPreferredPrototypeEntity(null, adaptorName, null, connectionDictionary);
			}
		}
		return preferredPrototypEntity;
	}
	
	public EOEntity _getPreferredPrototypeEntity(String prefix, String adaptorName, String modelName, Map connectionDictionary) {
		EOEntity prototypeEntity = null;
		String driverPrototypeEntityName = _getDriverPrototypeEntityName(prefix, connectionDictionary, modelName);
		if (driverPrototypeEntityName != null && myModelGroup != null) {
			prototypeEntity = myModelGroup.getEntityNamed(driverPrototypeEntityName);
		}
		if (prototypeEntity == null) {
			String adaptorPrototypeEntityName = _getAdaptorPrototypeEntityName(prefix, adaptorName, modelName);
			if (adaptorPrototypeEntityName != null && myModelGroup != null) {
				prototypeEntity = myModelGroup.getEntityNamed(adaptorPrototypeEntityName);
			}
		}
		if (prototypeEntity == null && myModelGroup != null) {
			String defaultPrototypeEntityName = _getDefaultPrototypeEntityName(prefix, modelName);
			prototypeEntity = myModelGroup.getEntityNamed(defaultPrototypeEntityName);
		}
		return prototypeEntity;
	}

	public synchronized Set<EOAttribute> getPrototypeAttributes() {
		if (myPrototypeAttributeCache == null) {
			Map<String, EOAttribute> prototypeAttributeCache = new HashMap<String, EOAttribute>();

			Map connectionDictionary = null;
			EODatabaseConfig activeDatabaseConfig = getActiveDatabaseConfig();
			if (activeDatabaseConfig != null) {
				connectionDictionary = activeDatabaseConfig.getConnectionDictionary();
			}
			addPrototypeAttributes(_getDefaultPrototypeEntityName(null, null), prototypeAttributeCache);
			addPrototypeAttributes(_getAdaptorPrototypeEntityName(null, getAdaptorName(), null), prototypeAttributeCache);
			addPrototypeAttributes(_getDriverPrototypeEntityName(null, connectionDictionary, null), prototypeAttributeCache);
			
			addPrototypeAttributes(_getDefaultPrototypeEntityName("Custom", null), prototypeAttributeCache);
			addPrototypeAttributes(_getAdaptorPrototypeEntityName("Custom", getAdaptorName(), null), prototypeAttributeCache);
			addPrototypeAttributes(_getDriverPrototypeEntityName("Custom", connectionDictionary, null), prototypeAttributeCache);
			
			addPrototypeAttributes(_getDefaultPrototypeEntityName(null, getName()), prototypeAttributeCache);
			addPrototypeAttributes(_getAdaptorPrototypeEntityName(null, getAdaptorName(), getName()), prototypeAttributeCache);
			addPrototypeAttributes(_getDriverPrototypeEntityName(null, connectionDictionary, getName()), prototypeAttributeCache);

			if (activeDatabaseConfig != null) {
				EOEntity prototypeEntity = activeDatabaseConfig.getPrototype();
				if (prototypeEntity != null) {
					addPrototypeAttributes(prototypeEntity.getName(), prototypeAttributeCache);
				}
			}

			// Do we need to support "EOPrototypesToHide" entity?
			myPrototypeAttributeCache = new HashSet<EOAttribute>();
			myPrototypeAttributeCache.addAll(prototypeAttributeCache.values());
		}
		return myPrototypeAttributeCache;
	}

	protected void addPrototypeAttributes(String _prototypeEntityName, Map<String, EOAttribute> _prototypeAttributeCache) {
		if (_prototypeEntityName != null) {
			EOEntity prototypeEntity = myModelGroup.getEntityNamed(_prototypeEntityName);
			if (prototypeEntity != null) {
				for (EOAttribute prototypeAttribute : prototypeEntity.getAttributes()) {
					_prototypeAttributeCache.put(prototypeAttribute.getName(), prototypeAttribute);
				}
			}
		}
	}

	public EOAttribute getPrototypeAttributeNamed(String _name) {
		EOAttribute matchingAttribute = null;
		Set<EOAttribute> prototypeAttributes = getPrototypeAttributes();
		Iterator<EOAttribute> attributesIter = prototypeAttributes.iterator();
		while (matchingAttribute == null && attributesIter.hasNext()) {
			EOAttribute attribute = attributesIter.next();
			if (attribute.getName().equals(_name)) {
				matchingAttribute = attribute;
			}
		}
		return matchingAttribute;
	}

	/** End Prototypes * */

	/*
	 * public static void main(String[] args) throws IOException,
	 * EOModelException { Set<EOModelVerificationFailure> failures = new
	 * LinkedHashSet<EOModelVerificationFailure>();
	 * 
	 * EOModelGroup modelGroup = new EOModelGroup(); NullProgressMonitor
	 * progressMonitor = new NullProgressMonitor();
	 * modelGroup.loadModelsFromFolder(new
	 * File("/Library/Frameworks/ERPrototypes.framework/Resources").toURL(),
	 * failures, progressMonitor); modelGroup.loadModelsFromFolder(new
	 * File("/Users/mschrag/Documents/workspace/MDTask").toURL(), failures,
	 * progressMonitor); modelGroup.loadModelsFromFolder(new
	 * File("/Users/mschrag/Documents/workspace/MDTAccounting").toURL(),
	 * failures, progressMonitor); modelGroup.loadModelsFromFolder(new
	 * File("/Users/mschrag/Documents/workspace/MDTCMS").toURL(), failures,
	 * progressMonitor); modelGroup.loadModelsFromFolder(new
	 * File("/Users/mschrag/Documents/workspace/MDTWOExtensions").toURL(),
	 * failures, progressMonitor);
	 * 
	 * modelGroup.resolve(failures); modelGroup.verify(failures); Iterator
	 * failuresIter = failures.iterator(); while (failuresIter.hasNext()) {
	 * EOModelVerificationFailure failure = (EOModelVerificationFailure)
	 * failuresIter.next(); System.out.println("EOModel.main: " + failure); }
	 * 
	 * File outputPath = new File("/tmp"); System.out.println("EOModel.main:
	 * Saving model to " + outputPath + " ..."); EOModel mdtaskModel =
	 * modelGroup.getModelNamed("MDTask"); mdtaskModel.saveToFolder(outputPath);
	 * System.out.println("EOModel.main: Done."); }
	 */
}
