/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package database;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import ilog.views.gantt.IlvActivity;
import ilog.views.gantt.IlvConstraint;
import ilog.views.gantt.IlvConstraintType;
import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvHierarchyNode;
import ilog.views.gantt.IlvReservation;
import ilog.views.gantt.IlvResource;
import ilog.views.gantt.IlvTimeInterval;
import ilog.views.gantt.model.IlvAbstractActivity;
import ilog.views.gantt.model.IlvAbstractConstraint;
import ilog.views.gantt.model.IlvAbstractGanttModel;
import ilog.views.gantt.model.IlvAbstractReservation;
import ilog.views.gantt.model.IlvAbstractResource;

/**
 * <code>DBROGanttModel</code> is a read-only Gantt data model that connects to
 * an underlying {@link GanttDBRO}. The data model caches the results of
 * querying the database to optimize speed at the expense of memory usage.
 * Activities, resources, constraints, and reservations are defined as inner
 * classes.
 */
public class DBROGanttModel extends IlvAbstractGanttModel {
  // =========================================
  // Class Constants
  // =========================================

  /**
   * Log ID.
   */
  public static final String logID = "DBModel";

  // =========================================
  // Instance Variables
  // =========================================

  /**
   * The database.
   */
  private GanttDBRO db;

  // =========================================
  // Instance Construction and Initialization
  // =========================================

  /**
   * Creates a new <code>DBROGanttModel</code> that queries the specified
   * database.
   */
  public DBROGanttModel(GanttDBRO database) {
    db = database;
    initActivities();
    initResources();
    initConstraints();
    initReservations();
  }

  // =========================================
  // Accessing
  // =========================================

  /**
   * Returns the underlying database.
   */
  public GanttDBRO getDatabase() {
    return db;
  }

  // =========================================
  // Activities and Resources
  // =========================================

  /**
   * Returns whether the specified activity or resource is a member of the data
   * model.
   * 
   * @param activityOrResource
   *          The activity or resource.
   */
  Override
  public boolean contains(IlvHierarchyNode activityOrResource) {
    if (activityOrResource instanceof IlvActivity)
      return contains((IlvActivity) activityOrResource);
    else
      return contains((IlvResource) activityOrResource);
  }

  // =========================================
  // Activities
  // =========================================

  /**
   * The database lookup key of the root activity.
   */
  private String rootActivityKey;

  /**
   * Indicates whether <code>rootActivityKey</code> is valid.
   */
  private boolean rootActivityQueried;

  /**
   * Cached {@link ActivityState}s, indexed by their database lookup key.
   */
  private Map<String, ActivityState> activityCache;

  /**
   * The <code>ActivityState</code> inner class. Each instance maintains state
   * info on an activity's hierarchical relationships.
   */
  class ActivityState {
    /**
     * The activity for this state info.
     */
    Activity activity;

    /**
     * Indicates whether the parent ID for the activity has been queried. If
     * <code>true</code>, then the value in <code>parentKey</code> is valid.
     */
    boolean parentQueried;

    /**
     * The database lookup key of the activity's parent. If <code>null</code>,
     * then the activity is the root activity and has no parent. This value is
     * only valid if <code>parentQueried</code> is <code>true</code>.
     */
    String parentKey;

    /**
     * The list of lookup keys for the activity's children. If the child keys
     * have not been queried yet, then <code>childKeys</code> will be
     * <code>null</code>.
     */
    List<String> childKeys;

    /**
     * Creates an <code>ActivityState</code> for the specified activity.
     */
    public ActivityState(Activity activity) {
      this.activity = activity;
    }

    /**
     * Returns the activity.
     */
    public final Activity getActivity() {
      return activity;
    }

    /**
     * Returns the activity-state for the parent of the activity or
     * <code>null</code> if the activity is the root activity.
     */
    public final ActivityState getParentState() {
      if (!parentQueried) {
        Activity activity = getActivity();
        if (activity == getRootActivity())
          parentKey = null;
        else
          parentKey = getDatabase().queryActivityParent(getActivity().getDBKey());
        parentQueried = true;
      }
      if (parentKey == null)
        return null;
      return getActivityState(parentKey);
    }

    /**
     * Returns the parent of the activity or <code>null</code> if the activity
     * is the root activity.
     */
    public final Activity getParent() {
      ActivityState parentState = getParentState();
      if (parentState == null)
        return null;
      return parentState.getActivity();
    }

    /**
     * If needed, queries the database for the child activity lookup keys.
     */
    private final void initChildrenIfNeeded() {
      if (childKeys != null)
        return;
      String[] keyArray = getDatabase().queryActivityChildren(getActivity().getDBKey());
      childKeys = new ArrayList<String>(keyArray.length);
      for (int i = 0; i < keyArray.length; i++)
        childKeys.add(keyArray[i]);
    }

    /**
     * Returns the number of children.
     */
    public final int getChildCount() {
      initChildrenIfNeeded();
      return childKeys.size();
    }

    /**
     * Returns the activity-state for the child activity at the specified index.
     */
    public final ActivityState getChildState(int index) {
      initChildrenIfNeeded();
      String childKey = childKeys.get(index);
      return getActivityState(childKey);
    }

    /**
     * Returns the child activity at the specified index.
     */
    public final IlvActivity getChild(int index) {
      ActivityState childState = getChildState(index);
      return childState.getActivity();
    }

    /**
     * Returns the index of the specified child.
     */
    public final int getChildIndex(String dbKey) {
      initChildrenIfNeeded();
      return childKeys.indexOf(dbKey);
    }
  }

  /**
   * Initializes the collection of activities of this data model.
   */
  private void initActivities() {
    rootActivityKey = null;
    rootActivityQueried = false;
    activityCache = new HashMap<String, DBROGanttModel.ActivityState>();
  }

  /**
   * Verify that the specified activity is an instance of <code>Activity</code>.
   * If not, this method throws an <code>IllegalArgumentException</code>.
   */
  protected final Activity checkActivity(IlvActivity activity) {
    if (!(activity instanceof Activity))
      throw new IllegalArgumentException(activity + " is not a member of " + this);
    return (Activity) activity;
  }

  /**
   * Returns the activity-state for the specified activity. If the activity is
   * not already cached, an activity and its associated state object are created
   * and inserted into the cache.
   * 
   * @param dbKey
   *          The lookup key for the activity. It is assumed that the key
   *          represents a valid activity in the database.
   */
  protected final ActivityState getActivityState(String dbKey) {
    ActivityState activityState = activityCache.get(dbKey);
    if (activityState == null) {
      Activity a = createActivity(dbKey);
      activityState = new ActivityState(a);
      activityCache.put(dbKey, activityState);
    }
    return activityState;
  }

  /**
   * Creates a new activity object from the specified lookup key. It is assumed
   * that the key represents a valid activity in the database. In general, the
   * activity instance should not query the database immediately when it is
   * created. Instead, it should lazy query the database when properties are are
   * first accessed. Subclasses can override this method as needed to create
   * customized activities.
   * 
   * @param dbKey
   *          The database lookup key.
   */
  protected Activity createActivity(String dbKey) {
    Logger.getLogger(logID).log(Level.INFO, "[Model] Creating Activity instance for {0}", dbKey);
    Activity newActivity = new Activity(dbKey);
    return newActivity;
  }

  /**
   * Returns whether the specified activity is a member of the data model.
   * 
   * @param activity
   *          The activity.
   */
  protected synchronized boolean contains(IlvActivity activity) {
    if (!(activity instanceof Activity))
      return false;
    Activity a = (Activity) activity;
    String dbKey = a.getDBKey();
    ActivityState activityState = activityCache.get(dbKey);
    if (activityState != null)
      return true;
    GanttDBRO.ActivityRecord dbRecord = getDatabase().queryActivity(dbKey);
    if (dbRecord != null) {
      a.dbRecord = dbRecord;
      activityState = new ActivityState(a);
      activityCache.put(dbKey, activityState);
      return true;
    } else {
      return false;
    }
  }

  /**
   * Returns the root activity of the data model or <code>null</code> if the
   * data model contains no activities.
   */
  Override
  public synchronized IlvActivity getRootActivity() {
    if (!rootActivityQueried) {
      rootActivityKey = db.queryRootActivityKey();
      rootActivityQueried = true;
    }
    if (rootActivityKey == null)
      return null;
    ActivityState activityState = getActivityState(rootActivityKey);
    return activityState.getActivity();
  }

  /**
   * Sets the root activity of the data model.
   */
  Override
  public void setRootActivity(IlvActivity root) {
  }

  /**
   * Returns the parent activity of the specified activity or <code>null</code>
   * if the activity is the root activity of the data model.
   * 
   * @param activity
   *          The activity.
   */
  Override
  public synchronized IlvActivity getParentActivity(IlvActivity activity) {
    Activity a = checkActivity(activity);
    if (a == getRootActivity())
      return null;
    String id = a.getDBKey();
    ActivityState activityState = getActivityState(id);
    return activityState.getParent();
  }

  /**
   * Returns the index of the specified activity within its parent activity. If
   * the activity is the root activity of the data model, then -1 is returned.
   * 
   * @param activity
   *          The activity.
   */
  Override
  public synchronized int getParentActivityIndex(IlvActivity activity) {
    Activity a = checkActivity(activity);
    if (a == getRootActivity())
      return -1;
    String childKey = a.getDBKey();
    ActivityState childState = getActivityState(childKey);
    ActivityState parentState = childState.getParentState();
    return parentState.getChildIndex(childKey);
  }

  /**
   * Returns the number of children of the specified parent activity.
   * 
   * @param parent
   *          The parent activity.
   * @return The number of child activities.
   */
  Override
  public synchronized int getChildActivityCount(IlvActivity parent) {
    Activity p = checkActivity(parent);
    ActivityState parentState = getActivityState(p.getDBKey());
    return parentState.getChildCount();
  }

  /**
   * Returns the child of the specified parent activity at index
   * <code>index</code>.
   * 
   * @param parent
   *          The parent activity.
   * @param index
   *          The child index.
   * @return The child activity at index <code>index</code>.
   */
  Override
  public synchronized IlvActivity getChildActivity(IlvActivity parent, int index) {
    Activity p = checkActivity(parent);
    ActivityState parentState = getActivityState(p.getDBKey());
    return parentState.getChild(index);
  }

  /**
   * Returns the index of the specified child in the parent activity's list of
   * children.
   * 
   * @param parent
   *          The parent activity.
   * @param child
   *          The child activity to find the index of.
   * @return The index of the child activity, or -1 if the activity is not a
   *         child of parent.
   */
  Override
  public synchronized int getChildActivityIndex(IlvActivity parent, IlvActivity child) {
    Activity p = checkActivity(parent);
    Activity c = checkActivity(child);
    ActivityState parentState = getActivityState(p.getDBKey());
    return parentState.getChildIndex(c.getDBKey());
  }

  /**
   * Adds <i>newActivity</i> as a child of <i>parent</i> activity at the
   * specified location in its list of child activities.
   */
  Override
  public void addActivity(IlvActivity newActivity, IlvActivity parent, int index) {
  }

  /**
   * Removes the child activity from <i>parent</i> at the specified location in
   * its list of child activities.
   */
  Override
  public void removeActivity(IlvActivity parent, int index) {
  }

  /**
   * Removes the specified child <i>activity</i> from its parent.
   */
  Override
  public void removeActivity(IlvActivity activity) {
  }

  /**
   * Moves the specified activity from its current location in the tree to a new
   * location.
   */
  Override
  public void moveActivity(IlvActivity activity, IlvActivity newParent, int newIndex) {
  }

  /**
   * <code>Activity</code> is an inner class that implements a read-only
   * activity object.
   */
  public class Activity extends IlvAbstractActivity {
    private String key;
    private GanttDBRO.ActivityRecord dbRecord;
    private IlvTimeInterval interval; // duplicate of start and end in db record

    /**
     * Creates a new <code>Activity</code> from the specified database key.
     * 
     * @param key
     *          The database lookup key.
     */
    protected Activity(String key) {
      if (key == null)
        throw new IllegalArgumentException("Null database key");
      this.key = key;
    }

    /**
     * Queries the database for the properties of this activity.
     */
    protected void queryPropertiesIfNeeded() {
      if (dbRecord != null)
        return;
      dbRecord = getDatabase().queryActivity(key);
      interval = new IlvTimeInterval(dbRecord.getStart(), dbRecord.getEnd());
    }

    /**
     * Returns the database key of this activity.
     */
    public String getDBKey() {
      return key;
    }

    /**
     * Sets the data model to which the activity and all its children belong.
     */
    Override
    public void setGanttModelImpl(IlvGanttModel model) {
    }

    /**
     * Returns the name of the activity.
     */
    Override
    public String getName() {
      queryPropertiesIfNeeded();
      return dbRecord.getName();
    }

    /**
     * Sets the name of the activity.
     */
    Override
    public void setName(String name) {
    }

    /**
     * Returns the activity ID.
     */
    Override
    public String getID() {
      queryPropertiesIfNeeded();
      return dbRecord.getID();
    }

    /**
     * Sets the activity ID.
     */
    Override
    public void setID(String id) {
    }

    /**
     * Returns the time interval of the activity.
     */
    Override
    public IlvTimeInterval getTimeInterval() {
      queryPropertiesIfNeeded();
      return interval;
    }

    /**
     * Sets the time interval of the activity.
     */
    Override
    public void setTimeInterval(IlvTimeInterval interval) {
    }

    /**
     * Returns a parameter string that represents the state of this activity.
     */
    Override
    protected String paramString() {
      StringBuffer buf = new StringBuffer("key=");
      buf.append(getDBKey());
      buf.append(',');
      buf.append(super.paramString());
      return buf.toString();
    }

  }

  // =========================================
  // Resources
  // =========================================

  /**
   * The database lookup key of the root resource.
   */
  private String rootResourceKey;

  /**
   * Indicates whether <code>rootResourceKey</code> is valid.
   */
  private boolean rootResourceQueried;

  /**
   * Cached {@link ResourceState}s, indexed by their database lookup key.
   */
  private Map<String, ResourceState> resourceCache;

  /**
   * The <code>ResourceState</code> inner class. Each instance maintains state
   * info on a resource's hierarchical relationships.
   */
  class ResourceState {
    /**
     * The resource for this state info.
     */
    Resource resource;

    /**
     * Indicates whether the parent ID for the resource has been queried. If
     * <code>true</code>, then the value in <code>parentKey</code> is valid.
     */
    boolean parentQueried;

    /**
     * The database lookup key of the resource's parent. If <code>null</code>,
     * then the resource is the root resource and has no parent. This value is
     * only valid if <code>parentQueried</code> is <code>true</code>.
     */
    String parentKey;

    /**
     * The list of lookup keys for the resource's children. If the child keys
     * have not been queried yet, then <code>childKeys</code> will be
     * <code>null</code>.
     */
    List<String> childKeys;

    /**
     * Creates a <code>ResourceState</code> for the specified resource.
     */
    public ResourceState(Resource resource) {
      this.resource = resource;
    }

    /**
     * Returns the resource.
     */
    public final Resource getResource() {
      return resource;
    }

    /**
     * Returns the resource-state for the parent of the resource or
     * <code>null</code> if the resource is the root resource.
     */
    public final ResourceState getParentState() {
      if (!parentQueried) {
        Resource resource = getResource();
        if (resource == getRootResource())
          parentKey = null;
        else
          parentKey = getDatabase().queryResourceParent(getResource().getDBKey());
        parentQueried = true;
      }
      if (parentKey == null)
        return null;
      return getResourceState(parentKey);
    }

    /**
     * Returns the parent of the resource or <code>null</code> if the resource
     * is the root resource.
     */
    public final Resource getParent() {
      ResourceState parentState = getParentState();
      if (parentState == null)
        return null;
      return parentState.getResource();
    }

    /**
     * If needed, queries the database for the child resource lookup keys.
     */
    private final void initChildrenIfNeeded() {
      if (childKeys != null)
        return;
      String[] keyArray = getDatabase().queryResourceChildren(getResource().getDBKey());
      childKeys = new ArrayList<String>(keyArray.length);
      for (int i = 0; i < keyArray.length; i++)
        childKeys.add(keyArray[i]);
    }

    /**
     * Returns the number of children.
     */
    public final int getChildCount() {
      initChildrenIfNeeded();
      return childKeys.size();
    }

    /**
     * Returns the resource-state for the child resource at the specified index.
     */
    public final ResourceState getChildState(int index) {
      initChildrenIfNeeded();
      String childKey = childKeys.get(index);
      return getResourceState(childKey);
    }

    /**
     * Returns the child resource at the specified index.
     */
    public final IlvResource getChild(int index) {
      ResourceState childState = getChildState(index);
      return childState.getResource();
    }

    /**
     * Returns the index of the specified child.
     */
    public final int getChildIndex(String dbKey) {
      initChildrenIfNeeded();
      return childKeys.indexOf(dbKey);
    }
  }

  /**
   * Initializes the collection of resources of this data model.
   */
  private void initResources() {
    rootResourceKey = null;
    rootResourceQueried = false;
    resourceCache = new HashMap<String, DBROGanttModel.ResourceState>();
  }

  /**
   * Verify that the specified resource is an instance of <code>Resource</code>.
   * If not, this method throws an <code>IllegalArgumentException</code>.
   */
  protected final Resource checkResource(IlvResource resource) {
    if (!(resource instanceof Resource))
      throw new IllegalArgumentException(resource + " is not a member of " + this);
    return (Resource) resource;
  }

  /**
   * Returns the resource-state for the specified resource. If the resource is
   * not already cached, a resource and its associated state object are created
   * and inserted into the cache.
   * 
   * @param dbKey
   *          The lookup key for the resource. It is assumed that the key
   *          represents a valid resource in the database.
   */
  protected final ResourceState getResourceState(String dbKey) {
    ResourceState resourceState = resourceCache.get(dbKey);
    if (resourceState == null) {
      Resource r = createResource(dbKey);
      resourceState = new ResourceState(r);
      resourceCache.put(dbKey, resourceState);
    }
    return resourceState;
  }

  /**
   * Creates a new resource object from the specified lookup key. It is assumed
   * that the key represents a valid resource in the database. In general, the
   * resource instance should not query the database immediately when it is
   * created. Instead, it should lazy query the database when properties are are
   * first accessed. Subclasses can override this method as needed to create
   * customized resources.
   * 
   * @param dbKey
   *          The database lookup key.
   */
  protected Resource createResource(String dbKey) {
    Logger.getLogger(logID).log(Level.INFO, "[Model] Creating Resource instance for {0}", dbKey);
    Resource newResource = new Resource(dbKey);
    return newResource;
  }

  /**
   * Returns whether the specified resource is a member of the data model.
   * 
   * @param resource
   *          The resource.
   */
  protected synchronized boolean contains(IlvResource resource) {
    if (!(resource instanceof Resource))
      return false;
    Resource r = (Resource) resource;
    String dbKey = r.getDBKey();
    ResourceState resourceState = resourceCache.get(dbKey);
    if (resourceState != null)
      return true;
    GanttDBRO.ResourceRecord dbRecord = getDatabase().queryResource(dbKey);
    if (dbRecord != null) {
      r.dbRecord = dbRecord;
      resourceState = new ResourceState(r);
      resourceCache.put(dbKey, resourceState);
      return true;
    } else {
      return false;
    }
  }

  /**
   * Returns the root resource of the data model or <code>null</code> if the
   * data model contains no resources.
   */
  Override
  public synchronized IlvResource getRootResource() {
    if (!rootResourceQueried) {
      rootResourceKey = db.queryRootResourceKey();
      rootResourceQueried = true;
    }
    if (rootResourceKey == null)
      return null;
    ResourceState resourceState = getResourceState(rootResourceKey);
    return resourceState.getResource();
  }

  /**
   * Sets the root resource of the data model.
   */
  Override
  public void setRootResource(IlvResource root) {
  }

  /**
   * Returns the parent resource of the specified resource or <code>null</code>
   * if the resource is the root resource of the data model.
   * 
   * @param resource
   *          The resource.
   */
  Override
  public synchronized IlvResource getParentResource(IlvResource resource) {
    Resource r = checkResource(resource);
    if (r == getRootResource())
      return null;
    String id = r.getDBKey();
    ResourceState resourceState = getResourceState(id);
    return resourceState.getParent();
  }

  /**
   * Returns the index of the specified resource within its parent resource. If
   * the resource is the root resource of the data model, then -1 is returned.
   * 
   * @param resource
   *          The resource.
   */
  Override
  public synchronized int getParentResourceIndex(IlvResource resource) {
    Resource r = checkResource(resource);
    if (r == getRootResource())
      return -1;
    String childKey = r.getDBKey();
    ResourceState childState = getResourceState(childKey);
    ResourceState parentState = childState.getParentState();
    return parentState.getChildIndex(childKey);
  }

  /**
   * Returns the number of children of the specified parent resource.
   * 
   * @param parent
   *          The parent resource.
   * @return The number of child resources.
   */
  Override
  public synchronized int getChildResourceCount(IlvResource parent) {
    Resource p = checkResource(parent);
    ResourceState parentState = getResourceState(p.getDBKey());
    return parentState.getChildCount();
  }

  /**
   * Returns the child of the specified parent resource at index
   * <code>index</code>.
   * 
   * @param parent
   *          The parent resource.
   * @param index
   *          The child index.
   * @return The child resource at index <code>index</code>.
   */
  Override
  public synchronized IlvResource getChildResource(IlvResource parent, int index) {
    Resource p = checkResource(parent);
    ResourceState parentState = getResourceState(p.getDBKey());
    return parentState.getChild(index);
  }

  /**
   * Returns the index of the specified child in the parent resource's list of
   * children.
   * 
   * @param parent
   *          The parent resource.
   * @param child
   *          The child resource to find the index of.
   * @return The index of the child resource, or -1 if the resource is not a
   *         child of parent.
   */
  Override
  public synchronized int getChildResourceIndex(IlvResource parent, IlvResource child) {
    Resource p = checkResource(parent);
    Resource c = checkResource(child);
    ResourceState parentState = getResourceState(p.getDBKey());
    return parentState.getChildIndex(c.getDBKey());
  }

  /**
   * Adds <i>newResource</i> as a child of <i>parent</i> resource at the
   * specified location in its list of child resources.
   */
  Override
  public void addResource(IlvResource newResource, IlvResource parent, int index) {
  }

  /**
   * Removes the child resource from <i>parent</i> at the specified location in
   * its list of child resources.
   */
  Override
  public void removeResource(IlvResource parent, int index) {
  }

  /**
   * Removes the specified child <i>resource</i> from its parent.
   */
  Override
  public void removeResource(IlvResource resource) {
  }

  /**
   * Moves the specified resource from its current location in the tree to a new
   * location.
   */
  Override
  public void moveResource(IlvResource resource, IlvResource newParent, int newIndex) {
  }

  /**
   * <code>Resource</code> is an inner class that implements a read-only
   * resource object.
   */
  public class Resource extends IlvAbstractResource {
    private String key;
    private GanttDBRO.ResourceRecord dbRecord;

    /**
     * Creates a new <code>Resource</code> from the specified database key.
     * 
     * @param key
     *          The database lookup key.
     */
    protected Resource(String key) {
      if (key == null)
        throw new IllegalArgumentException("Null database key");
      this.key = key;
    }

    /**
     * Queries the database for the properties of this resource.
     */
    protected void queryPropertiesIfNeeded() {
      if (dbRecord != null)
        return;
      dbRecord = getDatabase().queryResource(key);
    }

    /**
     * Returns the database key of this resource.
     */
    public String getDBKey() {
      return key;
    }

    /**
     * Sets the data model to which the resource and all its children belong.
     */
    Override
    public void setGanttModelImpl(IlvGanttModel model) {
    }

    /**
     * Returns the name of the resource.
     */
    Override
    public String getName() {
      queryPropertiesIfNeeded();
      return dbRecord.getName();
    }

    /**
     * Sets the name of the resource.
     */
    Override
    public void setName(String name) {
    }

    /**
     * Returns the resource ID.
     */
    Override
    public String getID() {
      queryPropertiesIfNeeded();
      return dbRecord.getID();
    }

    /**
     * Sets the resource ID.
     */
    Override
    public void setID(String id) {
    }

    /**
     * Returns the resource's quantity.
     */
    Override
    public float getQuantity() {
      queryPropertiesIfNeeded();
      return dbRecord.getQuantity();
    }

    /**
     * Sets the resource's quantity.
     */
    Override
    public void setQuantity(float x) {
    }

    /**
     * Returns a parameter string that represents the state of this resource.
     */
    Override
    protected String paramString() {
      StringBuffer buf = new StringBuffer("key=");
      buf.append(getDBKey());
      buf.append(',');
      buf.append(super.paramString());
      return buf.toString();
    }

  }

  // =========================================
  // Constraints
  // =========================================

  /**
   * Cached constraints, indexed by their database lookup key.
   */
  private Map<String, Constraint> constraintCache;

  /**
   * Initializes the collection of constraints of this data model.
   */
  private void initConstraints() {
    constraintCache = new HashMap<String, DBROGanttModel.Constraint>();
  }

  /**
   * Returns the constraint specified by its lookup key. If the constraint is
   * not already cached, a constraint object is created and inserted into the
   * cache.
   * 
   * @param dbKey
   *          The lookup key for the constraint. It is assumed that the key
   *          represents a valid constraint in the database.
   */
  protected final IlvConstraint getConstraint(String dbKey) {
    Constraint constraint = constraintCache.get(dbKey);
    if (constraint == null) {
      constraint = createConstraint(dbKey);
      constraintCache.put(dbKey, constraint);
    }
    return constraint;
  }

  /**
   * Creates a new constraint object from the specified lookup key. It is
   * assumed that the key represents a valid constraint in the database. In
   * general, the constraint instance should not query the database immediately
   * when it is created. Instead, it should lazy query the database when
   * properties are first accessed. Subclasses can override this method as
   * needed to create customized constraints.
   * 
   * @param dbKey
   *          The database lookup key.
   */
  protected Constraint createConstraint(String dbKey) {
    Logger.getLogger(logID).log(Level.INFO, "[Model] Creating Constraint instance for {0}", dbKey);
    Constraint newConstraint = new Constraint(dbKey);
    return newConstraint;
  }

  /**
   * Returns whether the specified constraint is a member of the data model.
   * 
   * @param constraint
   *          The constraint.
   */
  Override
  public synchronized boolean contains(IlvConstraint constraint) {
    if (!(constraint instanceof Constraint))
      return false;
    Constraint c = (Constraint) constraint;
    String dbKey = c.getDBKey();
    if (constraintCache.get(dbKey) != null)
      return true;
    GanttDBRO.ConstraintRecord dbRecord = getDatabase().queryConstraint(dbKey);
    if (dbRecord != null) {
      c.dbRecord = dbRecord;
      constraintCache.put(dbKey, c);
      return true;
    } else {
      return false;
    }
  }

  /**
   * Returns an iterator that will traverse over the constraints specified by an
   * array of database keys.
   */
  protected Iterator<IlvConstraint> constraintIterator(final String[] constraintKeys) {
    return new Iterator<IlvConstraint>() {
      Iterator<String> keyIterator = Arrays.asList(constraintKeys).iterator();

      Override
      public boolean hasNext() {
        return keyIterator.hasNext();
      }

      Override
      public IlvConstraint next() {
        String dbKey = keyIterator.next();
        return getConstraint(dbKey);
      }

      Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  /**
   * Return an iterator over the constraints in the data model.
   */
  Override
  public Iterator<IlvConstraint> constraintIterator() {
    return constraintIterator(db.queryConstraints());
  }

  /**
   * Returns an iterator over the constraints in the data model that have the
   * specified activity as their source or <i>from</i> activity.
   */
  Override
  public Iterator<IlvConstraint> constraintIteratorFromActivity(IlvActivity fromActivity) {
    return constraintIterator(db.queryConstraintsFromActivity(((Activity) fromActivity).getDBKey()));
  }

  /**
   * Returns an iterator over the constraints in the data model that have the
   * specified activity as their target or <i>to</i> activity.
   */
  Override
  public Iterator<IlvConstraint> constraintIteratorToActivity(IlvActivity toActivity) {
    return constraintIterator(db.queryConstraintsToActivity(((Activity) toActivity).getDBKey()));
  }

  /**
   * Adds a constraint to the data model.
   */
  Override
  public void addConstraint(IlvConstraint newConstraint) {
  }

  /**
   * Removes the specified <i>constraint</i> from the data model.
   */
  Override
  public void removeConstraint(IlvConstraint constraint) {
  }

  /**
   * <code>Constraint</code> is an inner class that implements a read-only
   * constraint object.
   */
  public class Constraint extends IlvAbstractConstraint {
    private String key;
    private GanttDBRO.ConstraintRecord dbRecord;

    /**
     * Creates a new <code>Constraint</code> from the specified database key.
     * 
     * @param key
     *          The database lookup key.
     */
    private Constraint(String key) {
      if (key == null)
        throw new IllegalArgumentException("Null database key");
      this.key = key;
    }

    /**
     * Queries the database for the properties of this constraint.
     */
    protected void queryPropertiesIfNeeded() {
      if (dbRecord != null)
        return;
      dbRecord = getDatabase().queryConstraint(key);
    }

    /**
     * Returns this constraint's database key.
     */
    public String getDBKey() {
      return key;
    }

    /**
     * Sets the data model to which the resource belongs.
     */
    Override
    public void setGanttModelImpl(IlvGanttModel model) {
    }

    /**
     * Returns the type of this constraint.
     */
    Override
    public IlvConstraintType getType() {
      queryPropertiesIfNeeded();
      return dbRecord.getType();
    }

    /**
     * Sets the type of this constraint.
     */
    Override
    public void setType(IlvConstraintType type) {
    }

    /**
     * Returns the source or <i>From</i> activity for this constraint.
     */
    Override
    public IlvActivity getFromActivity() {
      queryPropertiesIfNeeded();
      ActivityState activityState = getActivityState(dbRecord.getFromActivityKey());
      return activityState.getActivity();
    }

    /**
     * Returns the target or <i>To</i> activity for this constraint.
     */
    Override
    public IlvActivity getToActivity() {
      queryPropertiesIfNeeded();
      ActivityState activityState = getActivityState(dbRecord.getToActivityKey());
      return activityState.getActivity();
    }

  }

  // =========================================
  // Reservations
  // =========================================

  /**
   * Cached reservations, indexed by their database lookup key.
   */
  private Map<String, Reservation> reservationCache;

  /**
   * Initializes the collection of reservations of this data model.
   */
  private void initReservations() {
    reservationCache = new HashMap<String, DBROGanttModel.Reservation>();
  }

  /**
   * Returns the reservation specified by its lookup key. If the reservation is
   * not already cached, a reservation object is created and inserted into the
   * cache.
   * 
   * @param dbKey
   *          The lookup key for the reservation. It is assumed that the key
   *          represents a valid reservation in the database.
   */
  protected final IlvReservation getReservation(String dbKey) {
    Reservation reservation = reservationCache.get(dbKey);
    if (reservation == null) {
      reservation = createReservation(dbKey);
      reservationCache.put(dbKey, reservation);
    }
    return reservation;
  }

  /**
   * Creates a new reservation object from the specified lookup key. It is
   * assumed that the key represents a valid reservation in the database. In
   * general, the reservation instance should not query the database immediately
   * when it is created. Instead, it should lazy query the database when
   * properties are first accessed. Subclasses can override this method as
   * needed to create customized reservations.
   * 
   * @param dbKey
   *          The database lookup key.
   */
  protected Reservation createReservation(String dbKey) {
    Logger.getLogger(logID).log(Level.INFO, "[Model] Creating Reservation instance for {0}", dbKey);
    Reservation newReservation = new Reservation(dbKey);
    return newReservation;
  }

  /**
   * Returns whether the specified reservation is a member of the data model.
   * 
   * @param reservation
   *          The reservation.
   */
  Override
  public synchronized boolean contains(IlvReservation reservation) {
    if (!(reservation instanceof Reservation))
      return false;
    Reservation r = (Reservation) reservation;
    String dbKey = r.getDBKey();
    if (reservationCache.get(dbKey) != null)
      return true;
    GanttDBRO.ReservationRecord dbRecord = getDatabase().queryReservation(dbKey);
    if (dbRecord != null) {
      r.dbRecord = dbRecord;
      reservationCache.put(dbKey, r);
      return true;
    } else {
      return false;
    }
  }

  /**
   * Returns an iterator that will traverse over the reservations specified by
   * an array of database keys.
   */
  protected Iterator<IlvReservation> reservationIterator(final String[] reservationKeys) {
    return new Iterator<IlvReservation>() {
      Iterator<String> keyIterator = Arrays.asList(reservationKeys).iterator();

      Override
      public boolean hasNext() {
        return keyIterator.hasNext();
      }

      Override
      public IlvReservation next() {
        String dbKey = keyIterator.next();
        return getReservation(dbKey);
      }

      Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  /**
   * Returns an iterator over all of the reservations in the data model.
   */
  Override
  public Iterator<IlvReservation> reservationIterator() {
    return reservationIterator(db.queryReservations());
  }

  /**
   * Returns an iterator over all of the reservations in the data model that are
   * associated with the specified activity.
   */
  Override
  public Iterator<IlvReservation> reservationIterator(IlvActivity activity) {
    return reservationIterator(db.queryReservationsForActivity(((Activity) activity).getDBKey()));
  }

  /**
   * Returns an iterator over all of the reservations in the data model that are
   * associated with the specified resource.
   */
  Override
  public Iterator<IlvReservation> reservationIterator(IlvResource resource) {
    return reservationIterator(db.queryReservationsForResource(((Resource) resource).getDBKey()));
  }

  /**
   * Returns an iterator over all of the reservations in the data model that are
   * associated with the specified resource and where the assigned activity
   * intersects the specified time interval.
   */
  Override
  public Iterator<IlvReservation> reservationIterator(IlvResource resource, IlvTimeInterval interval) {
    return reservationIterator(
        db.queryReservationsForResource(((Resource) resource).getDBKey(), interval.getStart(), interval.getEnd()));
  }

  /**
   * Adds a reservation to the data model.
   */
  Override
  public void addReservation(IlvReservation newReservation) {
  }

  /**
   * Removes the specified <i>reservation</i> from the data model.
   */
  Override
  public void removeReservation(IlvReservation reservation) {
  }

  /**
   * <code>Reservation</code> is an inner class that implements a read-only
   * reservation object.
   */
  public class Reservation extends IlvAbstractReservation {
    private String key;
    private GanttDBRO.ReservationRecord dbRecord;

    /**
     * Creates a new <code>Reservation</code> from the specified database key.
     * 
     * @param key
     *          The database lookup key.
     */
    private Reservation(String key) {
      if (key == null)
        throw new IllegalArgumentException("Null database key");
      this.key = key;
    }

    /**
     * Queries the database for the properties of this reservation.
     */
    protected void queryPropertiesIfNeeded() {
      if (dbRecord != null)
        return;
      dbRecord = getDatabase().queryReservation(key);
    }

    /**
     * Returns this reservation's database key.
     */
    public String getDBKey() {
      return key;
    }

    /**
     * Sets the data model to which the resource belongs.
     */
    Override
    public void setGanttModelImpl(IlvGanttModel model) {
    }

    /**
     * Returns the resource for this reservation.
     */
    Override
    public IlvResource getResource() {
      queryPropertiesIfNeeded();
      ResourceState resourceState = getResourceState(dbRecord.getResourceKey());
      return resourceState.getResource();
    }

    /**
     * Sets the resource for this reservation.
     */
    Override
    public void setResource(IlvResource resource) {
    }

    /**
     * Returns the activity for this reservation.
     */
    Override
    public IlvActivity getActivity() {
      queryPropertiesIfNeeded();
      ActivityState activityState = getActivityState(dbRecord.getActivityKey());
      return activityState.getActivity();
    }

    /**
     * Returns a parameter string that represents the state of this reservation.
     */
    Override
    protected String paramString() {
      StringBuffer buf = new StringBuffer("key=");
      buf.append(getDBKey());
      buf.append(',');
      buf.append(super.paramString());
      return buf.toString();
    }

  }

}