content: /*****************************************************************
   *   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
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  
  package org.apache.cayenne.map;
  
  import org.apache.cayenne.ObjectId;
  import org.apache.cayenne.Persistent;
  import org.apache.cayenne.configuration.ConfigurationNode;
  import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
  import org.apache.cayenne.configuration.DataChannelDescriptor;
  import org.apache.cayenne.map.event.DbEntityListener;
  import org.apache.cayenne.map.event.EntityEvent;
  import org.apache.cayenne.map.event.ObjEntityListener;
  import org.apache.cayenne.resource.Resource;
  import org.apache.cayenne.util.ToStringBuilder;
  import org.apache.cayenne.util.Util;
  import org.apache.cayenne.util.XMLEncoder;
  import org.apache.cayenne.util.XMLSerializable;
  
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.TreeMap;
  
  import static java.util.Collections.emptyList;
  
  /**
   * Stores a collection of related mapping objects that describe database and
   * object layers of an application. DataMap contains DbEntities mapping database
   * tables, ObjEntities - mapping persistent Java classes, Procedures - mapping
   * database stored procedures.
   */
  public class DataMap implements Serializable, ConfigurationNode, XMLSerializable, MappingNamespace, DbEntityListener,
  		ObjEntityListener, Comparable<DataMap> {
  
  	private static final long serialVersionUID = 4851901426473991657L;
  
  	/**
  	 * Defines the name of the property for default DB catalog.
  	 * 
  	 * @since 4.0
  	 */
  	public static final String DEFAULT_CATALOG_PROPERTY = "defaultCatalog";
  
  	/**
  	 * Defines the name of the property for default DB schema.
  	 * 
  	 * @since 1.1
  	 */
  	public static final String DEFAULT_SCHEMA_PROPERTY = "defaultSchema";
  
  	/**
  	 * Defines the name of the property for default Java class package.
  	 * 
  	 * @since 1.1
  	 */
  	public static final String DEFAULT_PACKAGE_PROPERTY = "defaultPackage";
  
  	/**
  	 * Defines the name of the property for default Java superclass.
  	 * 
  	 * @since 1.1
  	 */
  	public static final String DEFAULT_SUPERCLASS_PROPERTY = "defaultSuperclass";
  
  	/**
  	 * Defines the name of the property for default DB schema.
  	 * 
  	 * @since 1.1
  	 */
  	public static final String DEFAULT_LOCK_TYPE_PROPERTY = "defaultLockType";
  
  	public static final String DEFAULT_QUOTE_SQL_IDENTIFIERS_PROPERTY = "quoteSqlIdentifiers";
  
  	/**
  	 * The namespace in which the data map XML file will be created. This is
  	 * also the URI to locate a copy of the schema document.
  	 */
  	public static final String SCHEMA_XSD = "http://cayenne.apache.org/schema/11/modelMap";
      public static final String SCHEMA_XSD_LOCATION = "https://cayenne.apache.org/schema/11/modelMap.xsd";
  
  	protected String name;
  	protected String location;
  	protected MappingNamespace namespace;
  
  	protected Boolean quotingSQLIdentifiers;
  
  	protected String defaultCatalog;
  	protected String defaultSchema;
  	protected String defaultPackage;
  
  	protected String defaultSuperclass;
  	protected int defaultLockType;
  
  	private Map<String, Embeddable> embeddablesMap;
      private Map<String, ObjEntity> objEntityMap;
      private Map<String, DbEntity> dbEntityMap;
      private Map<String, Procedure> procedureMap;
      private Map<String, QueryDescriptor> queryDescriptorMap;
      private Map<String, SQLResult> results;
  
  	/**
  	 * @since 3.1
  	 */
  	protected transient Resource configurationSource;
  
  	/**
  	 * @since 3.1
  	 */
  	protected DataChannelDescriptor dataChannelDescriptor;
  
  	/**
  	 * Creates a new unnamed DataMap.
  	 */
  	public DataMap() {
  		this(null);
  	}
  
  	/**
  	 * Creates a new named DataMap.
  	 */
  	public DataMap(String mapName) {
  		this(mapName, Collections.emptyMap());
  	}
  
  	public DataMap(String mapName, Map<String, Object> properties) {
  		embeddablesMap = new HashMap<>();
  		objEntityMap = new HashMap<>();
  		dbEntityMap = new HashMap<>();
  		procedureMap = new HashMap<>();
  		queryDescriptorMap = new HashMap<>();
  		results = new HashMap<>();
  		setName(mapName);
  		initWithProperties(properties);
  	}
  
  	/**
  	 * @since 3.1
  	 */
  	public DataChannelDescriptor getDataChannelDescriptor() {
  		return dataChannelDescriptor;
  	}
  
  	/**
  	 * @since 3.1
  	 */
  	public void setDataChannelDescriptor(DataChannelDescriptor dataChannelDescriptor) {
  		this.dataChannelDescriptor = dataChannelDescriptor;
  	}
  
  	/**
  	 * @since 3.1
  	 */
  	public <T> T acceptVisitor(ConfigurationNodeVisitor<T> visitor) {
  		return visitor.visitDataMap(this);
  	}
  
  	/**
  	 * @since 3.1
  	 */
  	public int compareTo(DataMap o) {
  		String o1 = getName();
  		String o2 = o.getName();
  
  		if (o1 == null) {
  			return (o2 != null) ? -1 : 0;
  		} else if (o2 == null) {
  			return 1;
  		} else {
  			return o1.compareTo(o2);
  		}
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public boolean isQuotingSQLIdentifiers() {
  		return quotingSQLIdentifiers;
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public void setQuotingSQLIdentifiers(boolean quotingSqlIdentifiers) {
  		this.quotingSQLIdentifiers = quotingSqlIdentifiers;
  	}
  
  	/**
  	 * Performs DataMap initialization from a set of properties, using defaults
  	 * for the missing properties.
  	 * 
  	 * @since 1.1
  	 */
  	public void initWithProperties(Map<String, Object> properties) {
  		// must init defaults even if properties are empty
  		if (properties == null) {
  			properties = Collections.emptyMap();
  		}
  
  		Object lockType = properties.get(DEFAULT_LOCK_TYPE_PROPERTY);
  		Object packageName = properties.get(DEFAULT_PACKAGE_PROPERTY);
  		Object catalog = properties.get(DEFAULT_CATALOG_PROPERTY);
  		Object schema = properties.get(DEFAULT_SCHEMA_PROPERTY);
  		Object superclass = properties.get(DEFAULT_SUPERCLASS_PROPERTY);
  		Object quoteSqlIdentifier = properties.get(DEFAULT_QUOTE_SQL_IDENTIFIERS_PROPERTY);
  
  		this.defaultLockType = "optimistic".equals(lockType) ? ObjEntity.LOCK_TYPE_OPTIMISTIC
  				: ObjEntity.LOCK_TYPE_NONE;
  
  		this.defaultPackage = (packageName != null) ? packageName.toString() : null;
  		this.quotingSQLIdentifiers = (quoteSqlIdentifier != null) && "true".equalsIgnoreCase(quoteSqlIdentifier.toString());
  		this.defaultSchema = (schema != null) ? schema.toString() : null;
  		this.defaultCatalog = (catalog != null) ? catalog.toString() : null;
  		this.defaultSuperclass = (superclass != null) ? superclass.toString() : null;
  	}
  
  	/**
  	 * Prints itself as XML to the provided PrintWriter.
  	 * 
  	 * @since 1.1
  	 */
  	public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
  		encoder.start("data-map")
  				.attribute("xmlns", SCHEMA_XSD)
  				.attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", true)
  				.attribute("xsi:schemaLocation", SCHEMA_XSD + " " + SCHEMA_XSD_LOCATION, true)
  				.projectVersion()
  				// properties
  				.property(DEFAULT_LOCK_TYPE_PROPERTY, defaultLockType)
  				.property(DEFAULT_PACKAGE_PROPERTY, defaultPackage)
  				.property(DEFAULT_CATALOG_PROPERTY, defaultCatalog)
  				.property(DEFAULT_SCHEMA_PROPERTY, defaultSchema)
  				.property(DEFAULT_SUPERCLASS_PROPERTY, defaultSuperclass)
  				.property(DEFAULT_QUOTE_SQL_IDENTIFIERS_PROPERTY, quotingSQLIdentifiers)
  				// elements
  				.nested(new TreeMap<>(getEmbeddableMap()), delegate)
  				.nested(new TreeMap<>(getProcedureMap()), delegate)
  				.nested(new TreeMap<>(getDbEntityMap()), delegate)
  				.nested(new TreeMap<>(getObjEntityMap()), delegate);
  
  		// and finally relationships
  		encodeDbRelationshipsAsXML(encoder, delegate);
  		encodeObjRelationshipsAsXML(encoder, delegate);
  
  		// descriptors at the end just to keep logic from older versions
  		encoder.nested(new TreeMap<>(getQueryDescriptorMap()), delegate);
  
  		delegate.visitDataMap(this);
  		encoder.end();
  	}
  
  	// stores relationships for the map of entities
  	private void encodeDbRelationshipsAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
  		for (DbEntity entity : new TreeMap<>(getDbEntityMap()).values()) {
  			entity.getRelationships().stream()
  					.filter(r -> !r.isRuntime())
  					.sorted(Comparator.comparing(Relationship::getName))
  					.forEach(r -> r.encodeAsXML(encoder, delegate));
  		}
  	}
  
  	// stores relationships for the map of entities
  	private void encodeObjRelationshipsAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
  		for (ObjEntity entity : new TreeMap<>(getObjEntityMap()).values()) {
  			entity.getDeclaredRelationships().stream()
  					.filter(r -> !r.isRuntime())
  					.sorted(Comparator.comparing(Relationship::getName))
  					.forEach(r -> r.encodeAsXML(encoder, delegate));
  		}
  	}
  
  	@Override
  	public String toString() {
  		return new ToStringBuilder(this).append("name", getName()).toString();
  	}
  
  	/**
  	 * Returns the name of this DataMap.
  	 */
  	public String getName() {
  		return name;
  	}
  
  	/**
  	 * Set the name of this DataMap.
  	 */
  	public void setName(String name) {
  		this.name = name;
  	}
  
  	/**
  	 * Adds all Object and DB entities and Queries from another map to this map.
  	 * Overwrites all existing entities and queries with the new ones.
  	 * <p>
  	 * <i>TODO: will need to implement advanced merge that allows different
  	 * policies for overwriting entities / queries. </i>
  	 * </p>
  	 */
  	public void mergeWithDataMap(DataMap map) {
  		for (DbEntity ent : new ArrayList<>(map.getDbEntities())) {
  			this.removeDbEntity(ent.getName());
  			this.addDbEntity(ent);
  		}
  
  		for (ObjEntity ent : new ArrayList<>(map.getObjEntities())) {
  			this.removeObjEntity(ent.getName());
  			this.addObjEntity(ent);
  		}
  
  		for (QueryDescriptor query : new ArrayList<>(map.getQueryDescriptors())) {
  			this.removeQueryDescriptor(query.getName());
  			this.addQueryDescriptor(query);
  		}
  	}
  
  	/**
  	 * Returns "location" property value. Location is abstract and can depend on
  	 * how the DataMap was loaded. E.g. location can be a File on the filesystem
  	 * or a location within a JAR.
  	 */
  	public String getLocation() {
  		return location;
  	}
  
  	/**
  	 * Sets "location" property.
  	 */
  	public void setLocation(String location) {
  		this.location = location;
  	}
  
  	/**
  	 * Returns a unmodifiable map of ObjEntities contained in this
  	 * DataMap, keyed by ObjEntity name.
  	 */
  	public Map<String, ObjEntity> getObjEntityMap() {
  		return Collections.unmodifiableMap(objEntityMap);
  	}
  
  	/**
  	 * Returns a unmodifiable map of DbEntities contained in this
  	 * DataMap, keyed by DbEntity name.
  	 */
  	public Map<String, DbEntity> getDbEntityMap() {
  		return Collections.unmodifiableMap(dbEntityMap);
  	}
  
  	/**
  	 * Returns a named query associated with this DataMap.
  	 * 
  	 * @since 4.0
  	 */
  	public QueryDescriptor getQueryDescriptor(String queryName) {
  		QueryDescriptor queryDescriptor = queryDescriptorMap.get(queryName);
  		if (queryDescriptor != null) {
  			return queryDescriptor;
  		}
  
  		return namespace != null ? namespace.getQueryDescriptor(queryName) : null;
  	}
  
  	/**
  	 * Stores a query descriptor under its name.
  	 *
  	 * @since 1.1
  	 */
  	public void addQueryDescriptor(QueryDescriptor queryDescriptor) {
  		if (queryDescriptor == null) {
  			throw new NullPointerException("Can't add null query.");
  		}
  
  		if (queryDescriptor.getName() == null) {
  			throw new NullPointerException("Query name can't be null.");
  		}
  
  		// TODO: change method signature to return replaced procedure and make
  		// sure the
  		// Modeler handles it...
  		QueryDescriptor existingQueryDescriptor = queryDescriptorMap.get(queryDescriptor.getName());
  		if (existingQueryDescriptor != null) {
  			if (existingQueryDescriptor == queryDescriptor) {
  				return;
  			} else {
  				throw new IllegalArgumentException("An attempt to override entity '" + queryDescriptor.getName());
  			}
  		}
  
  		queryDescriptorMap.put(queryDescriptor.getName(), queryDescriptor);
  	}
  
  	/**
  	 * Removes a named query from the DataMap.
  	 * 
  	 * @since 4.0
  	 */
  	public void removeQueryDescriptor(String queryName) {
  		queryDescriptorMap.remove(queryName);
  	}
  
  	/**
  	 * Removes all stored embeddable objects from the map.
  	 * 
  	 * @since 3.0
  	 */
  	public void clearEmbeddables() {
  		embeddablesMap.clear();
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public void clearResultSets() {
  		results.clear();
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public void clearQueries() {
  		queryDescriptorMap.clear();
  	}
  
  	/**
  	 * @since 1.2
  	 */
  	public void clearObjEntities() {
  		objEntityMap.clear();
  	}
  
  	/**
  	 * @since 1.2
  	 */
  	public void clearDbEntities() {
  		dbEntityMap.clear();
  	}
  
  	/**
  	 * @since 1.2
  	 */
  	public void clearProcedures() {
  		procedureMap.clear();
  	}
  
  	/**
  	 * @since 4.0
       */
  	public Map<String, QueryDescriptor> getQueryDescriptorMap() {
  		return Collections.unmodifiableMap(queryDescriptorMap);
  	}
  
  	/**
  	 * Returns an unmodifiable collection of mapped queries.
  	 *
  	 * @since 4.0
       */
  	public Collection<QueryDescriptor> getQueryDescriptors() {
  		return Collections.unmodifiableCollection(queryDescriptorMap.values());
  	}
  
  	/**
  	 * Adds an embeddable object to the DataMap.
  	 * 
  	 * @since 3.0
  	 */
  	public void addEmbeddable(Embeddable embeddable) {
  		if (embeddable == null) {
  			throw new NullPointerException("Null embeddable");
  		}
  
  		if (embeddable.getClassName() == null) {
  			throw new NullPointerException("Attempt to add Embeddable with no class name.");
  		}
  
  		// TODO: change method signature to return replaced entity and make sure
  		// the
  		// Modeler handles it...
  		Object existing = embeddablesMap.get(embeddable.getClassName());
  		if (existing != null) {
  			if (existing == embeddable) {
  				return;
  			} else {
  				throw new IllegalArgumentException("An attempt to override embeddable '" + embeddable.getClassName());
  			}
  		}
  
  		embeddablesMap.put(embeddable.getClassName(), embeddable);
  		embeddable.setDataMap(this);
  	}
  
  	/**
  	 * Adds a named SQLResultSet to the DataMap.
  	 * 
  	 * @since 3.0
  	 */
  	public void addResult(SQLResult result) {
  		if (result == null) {
  			throw new NullPointerException("Null result");
  		}
  
  		if (result.getName() == null) {
  			throw new NullPointerException("Attempt to add resultSetMapping with no name.");
  		}
  
  		Object existing = results.get(result.getName());
  		if (existing != null) {
  			if (existing == result) {
  				return;
  			} else {
  				throw new IllegalArgumentException("An attempt to override resultSetMapping '" + result.getName());
  			}
  		}
  
  		results.put(result.getName(), result);
  	}
  
  	/**
  	 * Adds a new ObjEntity to this DataMap.
  	 */
  	public void addObjEntity(ObjEntity entity) {
  		if (entity.getName() == null) {
  			throw new NullPointerException("Attempt to add ObjEntity with no name.");
  		}
  
  		// TODO: change method signature to return replaced entity and make sure
  		// the
  		// Modeler handles it...
  		Object existingEntity = objEntityMap.get(entity.getName());
  		if (existingEntity != null) {
  			if (existingEntity == entity) {
  				return;
  			} else {
  				throw new IllegalArgumentException("An attempt to override entity '" + entity.getName());
  			}
  		}
  
  		objEntityMap.put(entity.getName(), entity);
  		entity.setDataMap(this);
  	}
  
  	/**
  	 * Adds a new DbEntity to this DataMap.
  	 */
  	public void addDbEntity(DbEntity entity) {
  		if (entity.getName() == null) {
  			throw new NullPointerException("Attempt to add DbEntity with no name.");
  		}
  
  		// TODO: change method signature to return replaced entity and make sure
  		// the
  		// Modeler handles it...
  		Object existingEntity = dbEntityMap.get(entity.getName());
  		if (existingEntity != null) {
  			if (existingEntity == entity) {
  				return;
  			} else {
  				throw new IllegalArgumentException("An attempt to override db entity '" + entity.getName()
  						+ "' in DataMap '" + getName() + "'");
  			}
  		}
  
  		dbEntityMap.put(entity.getName(), entity);
  		entity.setDataMap(this);
  	}
  
  	/**
  	 * Returns an unmodifiable collection of ObjEntities stored in this DataMap.
  	 */
  	public Collection<ObjEntity> getObjEntities() {
  		return Collections.unmodifiableCollection(objEntityMap.values());
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public Map<String, Embeddable> getEmbeddableMap() {
  		return Collections.unmodifiableMap(embeddablesMap);
  	}
  
  	/**
  	 * Returns a collection of {@link Embeddable} mappings stored in the
  	 * DataMap.
  	 * 
  	 * @since 3.0
  	 */
  	public Collection<Embeddable> getEmbeddables() {
  		return Collections.unmodifiableCollection(embeddablesMap.values());
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public Map<String, SQLResult> getResultsMap() {
  		return Collections.unmodifiableMap(results);
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public Collection<SQLResult> getResults() {
  		return Collections.unmodifiableCollection(results.values());
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public Embeddable getEmbeddable(String className) {
  		Embeddable e = embeddablesMap.get(className);
  		if (e != null) {
  			return e;
  		}
  
  		return namespace != null ? namespace.getEmbeddable(className) : null;
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public SQLResult getResult(String name) {
  		SQLResult rsMapping = results.get(name);
  		if (rsMapping != null) {
  			return rsMapping;
  		}
  
  		return namespace != null ? namespace.getResult(name) : null;
  	}
  
  	/**
  	 * Returns all DbEntities in this DataMap.
  	 */
  	public Collection<DbEntity> getDbEntities() {
  		return Collections.unmodifiableCollection(dbEntityMap.values());
  	}
  
  	/**
  	 * Returns DbEntity matching the <code>name</code> parameter. No
  	 * dependencies will be searched.
  	 */
  	public DbEntity getDbEntity(String dbEntityName) {
  		DbEntity entity = dbEntityMap.get(dbEntityName);
  
  		if (entity != null) {
  			return entity;
  		}
  
  		return namespace != null ? namespace.getDbEntity(dbEntityName) : null;
  	}
  
  	/**
  	 * Returns an ObjEntity for a Persistent class name.
  	 * 
  	 * @since 1.1
  	 */
  	public ObjEntity getObjEntityForJavaClass(String javaClassName) {
  		if (javaClassName == null) {
  			return null;
  		}
  
  		for (ObjEntity entity : getObjEntities()) {
  			if (javaClassName.equals(entity.getClassName())) {
  				return entity;
  			}
  		}
  
  		return null;
  	}
  
  	/**
  	 * Returns an ObjEntity for a given name. If it is not found in this
  	 * DataMap, it will search a parent EntityNamespace.
  	 */
  	public ObjEntity getObjEntity(String objEntityName) {
  		ObjEntity entity = objEntityMap.get(objEntityName);
  		if (entity != null) {
  			return entity;
  		}
  
  		return namespace != null ? namespace.getObjEntity(objEntityName) : null;
  	}
  
  	/**
  	 * Returns all ObjEntities mapped to the given DbEntity.
  	 */
  	public Collection<ObjEntity> getMappedEntities(DbEntity dbEntity) {
  		if (dbEntity == null) {
  			return emptyList();
  		}
  
  		Collection<ObjEntity> allEntities = (namespace != null) ? namespace.getObjEntities() : getObjEntities();
  
  		if (allEntities.isEmpty()) {
  			return emptyList();
  		}
  
  		Collection<ObjEntity> result = new ArrayList<>();
  		for (ObjEntity entity : allEntities) {
  			if (entity.getDbEntity() == dbEntity) {
  				result.add(entity);
  			}
  		}
  
  		return result;
  	}
  
  	/**
  	 * Removes an {@link Embeddable} descriptor with matching class name.
  	 * 
  	 * @since 3.0
  	 */
  	public void removeEmbeddable(String className) {
  		// TODO: andrus, 1/25/2007 - clean up references like removeDbEntity
  		// does.
  		embeddablesMap.remove(className);
  	}
  
  	/**
  	 * @since 3.0
  	 */
  	public void removeResult(String name) {
  		results.remove(name);
  	}
  
  	/**
  	 * "Dirty" remove of the DbEntity from the data map.
  	 */
  	public void removeDbEntity(String dbEntityName) {
  		removeDbEntity(dbEntityName, false);
  	}
  
  	/**
  	 * Removes DbEntity from the DataMap. If <code>clearDependencies</code> is
  	 * true, all DbRelationships that reference this entity are also removed.
  	 * ObjEntities that rely on this entity are cleaned up.
  	 * 
  	 * @since 1.1
  	 */
  	public void removeDbEntity(String dbEntityName, boolean clearDependencies) {
  		DbEntity dbEntityToDelete = dbEntityMap.remove(dbEntityName);
  
  		if (dbEntityToDelete != null && clearDependencies) {
  			for (DbEntity dbEnt : this.getDbEntities()) {
  				// take a copy since we're going to modify the entity
  				for (DbRelationship rel : new ArrayList<>(dbEnt.getRelationships())) {
  					if (dbEntityName.equals(rel.getTargetEntityName())) {
  						dbEnt.removeRelationship(rel.getName());
  					}
  				}
  			}
  
  			// Remove all obj relationships referencing removed DbRelationships.
  			for (ObjEntity objEnt : this.getObjEntities()) {
  				if (dbEntityToDelete.getName().equals(objEnt.getDbEntityName())) {
  					objEnt.clearDbMapping();
  				} else {
  					for (ObjRelationship rel : objEnt.getRelationships()) {
  						for (DbRelationship dbRel : rel.getDbRelationships()) {
  							if (dbRel.getTargetEntity() == dbEntityToDelete) {
  								rel.clearDbRelationships();
  								break;
  							}
  						}
  					}
  				}
  			}
  
  			MappingNamespace ns = getNamespace();
  			if (ns instanceof EntityResolver) {
  				((EntityResolver) ns).refreshMappingCache();
  			}
  		}
  	}
  
  	/**
  	 * "Dirty" remove of the ObjEntity from the data map.
  	 */
  	public void removeObjEntity(String objEntityName) {
  		removeObjEntity(objEntityName, false);
  	}
  
  	/**
  	 * Removes ObjEntity from the DataMap. If <code>clearDependencies</code> is
  	 * true, all ObjRelationships that reference this entity are also removed.
  	 * 
  	 * @since 1.1
  	 */
  	public void removeObjEntity(String objEntityName, boolean clearDependencies) {
  		ObjEntity entity = objEntityMap.remove(objEntityName);
  
  		if (entity != null && clearDependencies) {
  
  			// remove relationships that point to this entity
  			for (ObjEntity ent : getObjEntities()) {
  				// take a copy since we're going to modify the entity
  				for (ObjRelationship relationship : new ArrayList<>(ent.getRelationships())) {
  					if (objEntityName.equals(relationship.getSourceEntity().getName())
  							|| objEntityName.equals(relationship.getTargetEntityName())) {
  						ent.removeRelationship(relationship.getName());
  					}
  				}
  			}
  
  			MappingNamespace ns = getNamespace();
  			if (ns instanceof EntityResolver) {
  				((EntityResolver) ns).refreshMappingCache();
  			}
  		}
  	}
  
  	/**
  	 * Returns stored procedures associated with this DataMap.
  	 */
  	public Collection<Procedure> getProcedures() {
  		return Collections.unmodifiableCollection(procedureMap.values());
  	}
  
  	/**
  	 * Returns a Procedure for a given name or null if no such procedure exists.
  	 * If Procedure is not found in this DataMap, a parent EntityNamcespace is
  	 * searched.
  	 */
  	public Procedure getProcedure(String procedureName) {
  		Procedure procedure = procedureMap.get(procedureName);
  		if (procedure != null) {
  			return procedure;
  		}
  
  		return namespace != null ? namespace.getProcedure(procedureName) : null;
  	}
  
  	/**
  	 * Adds stored procedure to the list of procedures. If there is another
  	 * procedure registered under the same name, throws an
  	 * IllegalArgumentException.
  	 */
  	public void addProcedure(Procedure procedure) {
  		if (procedure.getName() == null) {
  			throw new NullPointerException("Attempt to add procedure with no name.");
  		}
  
  		// TODO: change method signature to return replaced procedure and make
  		// sure the
  		// Modeler handles it...
  		Object existingProcedure = procedureMap.get(procedure.getName());
  		if (existingProcedure != null) {
  			if (existingProcedure == procedure) {
  				return;
  			} else {
  				throw new IllegalArgumentException("An attempt to override procedure '" + procedure.getName());
  			}
  		}
  
  		procedureMap.put(procedure.getName(), procedure);
  		procedure.setDataMap(this);
  	}
  
  	public void removeProcedure(String name) {
  		procedureMap.remove(name);
  	}
  
  	/**
  	 * Returns a sorted unmodifiable map of Procedures in this DataMap keyed by
  	 * name.
  	 */
  	public Map<String, Procedure> getProcedureMap() {
  		return Collections.unmodifiableMap(procedureMap);
  	}
  
  	/**
  	 * Returns a parent namespace where this DataMap resides. Parent
  	 * EntityNamespace is used to establish relationships with entities in other
  	 * DataMaps.
  	 * 
  	 * @since 1.1
  	 */
  	public MappingNamespace getNamespace() {
  		return namespace;
  	}
  
  	/**
  	 * Sets a parent namespace where this DataMap resides. Parent
  	 * EntityNamespace is used to establish relationships with entities in other
  	 * DataMaps.
  	 * 
  	 * @since 1.1
  	 */
  	public void setNamespace(MappingNamespace namespace) {
  		this.namespace = namespace;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public int getDefaultLockType() {
  		return defaultLockType;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public void setDefaultLockType(int defaultLockType) {
  		this.defaultLockType = defaultLockType;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public String getDefaultPackage() {
  		return defaultPackage;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public void setDefaultPackage(String defaultPackage) {
  		this.defaultPackage = defaultPackage;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public String getDefaultSchema() {
  		return defaultSchema;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public void setDefaultSchema(String defaultSchema) {
  		this.defaultSchema = defaultSchema;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public String getDefaultSuperclass() {
  		return defaultSuperclass;
  	}
  
  	/**
  	 * @since 1.1
  	 */
  	public void setDefaultSuperclass(String defaultSuperclass) {
  		this.defaultSuperclass = defaultSuperclass;
  	}
  
  	/**
  	 * DbEntity property changed. May be name, attribute or relationship added
  	 * or removed, etc. Attribute and relationship property changes are handled
  	 * in respective listeners.
  	 * 
  	 * @since 1.2
  	 */
  	public void dbEntityChanged(EntityEvent e) {
  		Entity<?,?,?> entity = e.getEntity();
  		if (entity instanceof DbEntity) {
  
  			DbEntity dbEntity = (DbEntity) entity;
  
  			dbEntity.dbEntityChanged(e);
  
  			// finish up the name change here because we
  			// do not have direct access to the dbEntityMap
  			if (e.isNameChange()) {
  				// remove the entity from the map with the old name
  				dbEntityMap.remove(e.getOldName());
  
  				// add the entity back in with the new name
  				dbEntityMap.put(e.getNewName(), dbEntity);
  
  				// important - clear parent namespace:
  				MappingNamespace ns = getNamespace();
  				if (ns instanceof EntityResolver) {
  					((EntityResolver) ns).refreshMappingCache();
  				}
  			}
  		}
  	}
  
  	/** New entity has been created/added. */
  	public void dbEntityAdded(EntityEvent e) {
  		// does nothing currently
  	}
  
  	/** Entity has been removed. */
  	public void dbEntityRemoved(EntityEvent e) {
  		// does nothing currently
  	}
  
  	/**
  	 * ObjEntity property changed. May be name, attribute or relationship added
  	 * or removed, etc. Attribute and relationship property changes are handled
  	 * in respective listeners.
  	 * 
  	 * @since 1.2
  	 */
  	public void objEntityChanged(EntityEvent e) {
  		Entity<?,?,?> entity = e.getEntity();
  		if (entity instanceof ObjEntity) {
  
  			ObjEntity objEntity = (ObjEntity) entity;
  			objEntity.objEntityChanged(e);
  
  			// finish up the name change here because we
  			// do not have direct access to the objEntityMap
  			if (e.isNameChange()) {
  				// remove the entity from the map with the old name
  				objEntityMap.remove(e.getOldName());
  
  				// add the entity back in with the new name
  				objEntityMap.put(e.getNewName(), objEntity);
  
  				// important - clear parent namespace:
  				MappingNamespace ns = getNamespace();
  				if (ns instanceof EntityResolver) {
  					((EntityResolver) ns).refreshMappingCache();
  				}
  			}
  		}
  	}
  
  	/** New entity has been created/added. */
  	public void objEntityAdded(EntityEvent e) {
  		// does nothing currently
  	}
  
  	/** Entity has been removed. */
  	public void objEntityRemoved(EntityEvent e) {
  		// does nothing currently
  	}
  
  	/**
  	 * @since 3.1
  	 */
  	public Resource getConfigurationSource() {
  		return configurationSource;
  	}
  
  	/**
  	 * @since 3.1
  	 */
  	public void setConfigurationSource(Resource configurationSource) {
  		this.configurationSource = configurationSource;
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public String getDefaultCatalog() {
  		return defaultCatalog;
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public void setDefaultCatalog(String defaultCatalog) {
  		this.defaultCatalog = defaultCatalog;
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public EntityInheritanceTree getInheritanceTree(String entityName) {
  		// TODO: we should support that
  		throw new UnsupportedOperationException();
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public ObjEntity getObjEntity(Class<?> entityClass) {
  		if (entityClass == null) {
  			return null;
  		}
  
  		String className = entityClass.getName();
  
  		for (ObjEntity e : objEntityMap.values()) {
  			if (className.equals(e.getClassName())) {
  				return e;
  			}
  		}
  
  		return null;
  	}
  
  	public ObjEntity getObjEntity(Persistent object) {
  		ObjectId id = object.getObjectId();
  		if (id != null) {
  			return getObjEntity(id.getEntityName());
  		} else {
  			return getObjEntity(object.getClass());
  		}
  	}
  
  	public void clear() {
  		clearDbEntities();
  		clearEmbeddables();
  		clearObjEntities();
  		clearProcedures();
  		clearQueries();
  		clearResultSets();
  	}
  
      /**
       *
       * @return package + "." + name when it is possible otherwise just name
       *
       * @since 4.0
       */
      public String getNameWithDefaultPackage(String name) {
          return getNameWithPackage(defaultPackage, name);
      }
  
      /**
       *
       * @return package + "." + name when it is possible otherwise just name
       *
       * @since 4.0
       */
      public static String getNameWithPackage(String pack, String name) {
          if (Util.isEmptyString(pack)) {
              return name;
          } else {
              return pack + (pack.endsWith(".") ? ""  : ".") + name;
          }
      }
  
      public Map<String, ObjEntity> getSubclassesForObjEntity(ObjEntity superEntity) {
          Map<String, ObjEntity> subObjectEntities = new HashMap<>(5);
          for (ObjEntity objectEntity : objEntityMap.values()) {
              if (superEntity.getName().equals(objectEntity.getSuperEntityName())) {
                  subObjectEntities.put(objectEntity.getName(), objectEntity);
              }
          }
          return subObjectEntities;
      }
  
  }
  