package com.avaje.ebean.bean;

import com.avaje.ebean.Ebean;
import com.avaje.ebean.ValuePair;

import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * This is the object added to every entity bean using byte code enhancement.
 * <p>
 * This provides the mechanisms to support deferred fetching of reference beans
 * and oldValues generation for concurrency checking.
 * </p>
 */
public final class EntityBeanIntercept implements Serializable {

  private static final long serialVersionUID = -3664031775464862649L;

  private static final int STATE_NEW = 0;
  private static final int STATE_REFERENCE = 1;
  private static final int STATE_LOADED = 2;
  
  private transient NodeUsageCollector nodeUsageCollector;

  private transient PropertyChangeSupport pcs;

  private transient PersistenceContext persistenceContext;

  private transient BeanLoader beanLoader;

  private transient PreGetterCallback preGetterCallback;

  private String ebeanServerName;

  /**
   * The actual entity bean that 'owns' this intercept.
   */
  private final EntityBean owner;

  private EntityBean embeddedOwner;
  private int embeddedOwnerIndex;

  /**
   * One of NEW, REF, UPD.
   */
  private int state;
  
  private boolean readOnly;
  
  private boolean dirty;
  
  /**
   * Flag set to disable lazy loading - typically for SQL "report" type entity beans.
   */
  private boolean disableLazyLoad;

  /**
   * Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
   */
  private boolean lazyLoadFailure;

  /**
   * Used when a bean is partially filled.
   */
  private final boolean[] loadedProps;
  
  private boolean fullyLoadedBean;

  /**
   * Set of changed properties.
   */
  private boolean[] changedProps;
  
  /**
   * Flags indicating if a property is a dirty embedded bean. Used to distingush
   * between an embedded bean being completely overwritten and one of its
   * embedded properties being made dirty.
   */
  private boolean[] embeddedDirty;

  private Object[] origValues;

  private int lazyLoadProperty = -1;

  private Object ownerId;

  /**
   * Create a intercept with a given entity.
   * <p>
   * Refer to agent ProxyConstructor.
   * </p>
   */
  public EntityBeanIntercept(Object ownerBean) {
    this.owner = (EntityBean) ownerBean;
    this.loadedProps = new boolean[owner._ebean_getPropertyNames().length];
  }

  /**
   * Return the 'owning' entity bean.
   */
  public EntityBean getOwner() {
    return owner;
  }

  /**
   * Return the persistenceContext.
   */
  public PersistenceContext getPersistenceContext() {
    return persistenceContext;
  }

  /**
   * Set the persistenceContext.
   */
  public void setPersistenceContext(PersistenceContext persistenceContext) {
    this.persistenceContext = persistenceContext;
  }

  /**
   * Add a property change listener for this entity bean.
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    if (pcs == null) {
      pcs = new PropertyChangeSupport(owner);
    }
    pcs.addPropertyChangeListener(listener);
  }

  /**
   * Add a property change listener for this entity bean for a specific
   * property.
   */
  public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
    if (pcs == null) {
      pcs = new PropertyChangeSupport(owner);
    }
    pcs.addPropertyChangeListener(propertyName, listener);
  }

  /**
   * Remove a property change listener for this entity bean.
   */
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    if (pcs != null) {
      pcs.removePropertyChangeListener(listener);
    }
  }

  /**
   * Remove a property change listener for this entity bean for a specific
   * property.
   */
  public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
    if (pcs != null) {
      pcs.removePropertyChangeListener(propertyName, listener);
    }
  }

  /**
   * Turn on profile collection.
   */
  public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
    this.nodeUsageCollector = usageCollector;
  }

  /**
   * Return the owning bean for an embedded bean.
   */
  public Object getEmbeddedOwner() {
    return embeddedOwner;
  }
  
  /**
   * Return the property index (for the parent) of this embedded bean.
   */
  public int getEmbeddedOwnerIndex() {
    return embeddedOwnerIndex;
  }

  /**
   * Clear the getter callback.
   */
  public void clearGetterCallback() {
    this.preGetterCallback = null;
  }

  /**
   * Register the callback to be triggered when getter is called.
   * This is used primarily to automatically flush the JDBC batch.
   */
  public void registerGetterCallback(PreGetterCallback getterCallback) {
    this.preGetterCallback = getterCallback;
  }

  /**
   * Set the embedded beans owning bean.
   */
  public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
    this.embeddedOwner = parentBean;
    this.embeddedOwnerIndex = embeddedOwnerIndex;
  }

  /**
   * Set the BeanLoader with PersistenceContext.
   */
  public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
    this.beanLoader = beanLoader;
    this.persistenceContext = ctx;
    this.ebeanServerName = beanLoader.getName();
  }

  /**
   * Set the BeanLoader.
   */
  public void setBeanLoader(BeanLoader beanLoader) {
    this.beanLoader = beanLoader;
    this.ebeanServerName = beanLoader.getName();
  }

  public boolean isFullyLoadedBean() {
    return fullyLoadedBean;
  }

  public void setFullyLoadedBean(boolean fullyLoadedBean) {
    this.fullyLoadedBean = fullyLoadedBean;
  }

  /**
   * Check each property to see if the bean is partially loaded.
   */
  public boolean isPartial() {
    for (int i = 0; i < loadedProps.length; i++) {
      if (!loadedProps[i]) {
        return true;
      }
    }
    return false;
  }

  /**
   * Return true if this bean has been directly modified (it has oldValues) or
   * if any embedded beans are either new or dirty (and hence need saving).
   */
  public boolean isDirty() {
    return dirty;
  }

  /**
   * Called by an embedded bean onto its owner.
   */
  public void setEmbeddedDirty(int embeddedProperty) {
    this.dirty = true;
    setEmbeddedPropertyDirty(embeddedProperty);
  }
  
  public void setDirty(boolean dirty) {
    this.dirty = dirty;
  }

  /**
   * Return true if this entity bean is new and not yet saved.
   */
  public boolean isNew() {
    return state == STATE_NEW;
  }

  /**
   * Return true if the entity bean is new or dirty (and should be saved).
   */
  public boolean isNewOrDirty() {
    return isNew() || isDirty();
  }

  /**
   * Return true if only the Id property has been loaded.
   */
  public boolean hasIdOnly(int idIndex) {
    for (int i = 0; i < loadedProps.length; i++) {
      if (i == idIndex) {
        if (!loadedProps[i]) return false;
      } else if (loadedProps[i]) {
        return false; 
      }
    }
    return true;
  }
  
  /**
   * Return true if the entity is a reference.
   */
  public boolean isReference() {
    return state == STATE_REFERENCE;
  }

  /**
   * Set this as a reference object.
   */
  public void setReference(int idPos) {
    state = STATE_REFERENCE;
    if (idPos > -1) {
      // For cases where properties are set on constructor
      // set every non Id property to unloaded (for lazy loading)
      for (int i=0; i< loadedProps.length; i++) {
        if (i != idPos) { 
          loadedProps[i] = false;
        }
      }
    }
  }

  /**
   * Return true if the bean should be treated as readOnly. If a setter method
   * is called when it is readOnly an Exception is thrown.
   */
  public boolean isReadOnly() {
    return readOnly;
  }

  /**
   * Set the readOnly status. If readOnly then calls to setter methods through
   * an exception.
   */
  public void setReadOnly(boolean readOnly) {
    this.readOnly = readOnly;
  }

  /**
   * Return true if the entity has been loaded.
   */
  public boolean isLoaded() {
    return state == STATE_LOADED;
  }

  /**
   * Set the bean into NEW state.
   */
  public void setNew() {
    this.state = STATE_NEW;
  }

  /**
   * Set the loaded state to true.
   * <p>
   * Calls to setter methods after the bean is loaded can result in 'Old Values'
   * being created to support ConcurrencyMode.ALL
   * </p>
   * <p>
   * Worth noting that this is also set after a insert/update. By doing so it
   * 'resets' the bean for making further changes and saving again.
   * </p>
   */
  public void setLoaded() {
    this.state = STATE_LOADED;
    this.owner._ebean_setEmbeddedLoaded();
    this.lazyLoadProperty = -1;
    this.origValues = null;
    this.changedProps = null;
    this.dirty = false;
  }

  /**
   * When finished loading for lazy or refresh on an already partially populated
   * bean.
   */
  public void setLoadedLazy() {
    this.state = STATE_LOADED;
    this.lazyLoadProperty = -1;
  }

  /**
   * Set lazy load failure flag.
   */
  public void setLazyLoadFailure(Object ownerId) {
    this.lazyLoadFailure = true;
    this.ownerId = ownerId;
  }

  /**
   * Return true if the bean is marked as having failed lazy loading.
   */
  public boolean isLazyLoadFailure() {
    return lazyLoadFailure;
  }

  /**
   * Return true if lazy loading is disabled.
   */
  public boolean isDisableLazyLoad() {
    return disableLazyLoad;
  }

  /**
   * Set true to turn off lazy loading.
   * <p>
   * Typically used to disable lazy loading on SQL based report beans.
   * </p>
   */
  public void setDisableLazyLoad(boolean disableLazyLoad) {
    this.disableLazyLoad = disableLazyLoad;
  }

  /**
   * Set the loaded status for the embedded bean.
   */
  public void setEmbeddedLoaded(Object embeddedBean) {
    if (embeddedBean instanceof EntityBean) {
      EntityBean eb = (EntityBean) embeddedBean;
      eb._ebean_getIntercept().setLoaded();
    }
  }

  /**
   * Return true if the embedded bean is new or dirty and hence needs saving.
   */
  public boolean isEmbeddedNewOrDirty(Object embeddedBean) {

    if (embeddedBean == null) {
      // if it was previously set then the owning bean would
      // have oldValues containing the previous embedded bean
      return false;
    }
    if (embeddedBean instanceof EntityBean) {
      return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();

    } else {
      // non-enhanced so must assume it is new and needs to be saved
      return true;
    }
  }

  /**
   * Return the original value that was changed via an update.
   */
  public Object getOrigValue(int propertyIndex) {
    if (origValues == null) {
      return null;
    }
    return origValues[propertyIndex];
  }
  
  /**
   * Finds the index position of a given property. Returns -1 if the property
   * can not be found.
   */
  public int findProperty(String propertyName) {
    String[] names = owner._ebean_getPropertyNames();
    for (int i = 0; i < names.length; i++) {
      if (names[i].equals(propertyName)) {
        return i;
      }
    }
    return -1;
  }
  
  /**
   * Return the property name for the given property.
   */
  public String getProperty(int propertyIndex) {
    if (propertyIndex == -1) {
      return null;
    }
    return owner._ebean_getPropertyName(propertyIndex);
  }
  
  /**
   * Return the number of properties.s
   */
  public int getPropertyLength() {
    return owner._ebean_getPropertyNames().length;
  }

  /**
   * Set the loaded state of the property given it's name.
   */
  public void setPropertyLoaded(String propertyName, boolean loaded) {
    int position = findProperty(propertyName);
    if (position == -1) {
      throw new IllegalArgumentException("Property "+propertyName+" not found");
    }
    loadedProps[position] = loaded;
  }

  /**
   * Set the property to be treated as unloaded. Used for properties initialised in default
   * constructor.
   */
  public void setPropertyUnloaded(int propertyIndex) {
    loadedProps[propertyIndex] = false;
  }
  
  /**
   * Set the property to be loaded.
   */
  public void setLoadedProperty(int propertyIndex) {
    loadedProps[propertyIndex] = true;
  }

  /**
   * Return true if the property is loaded.
   */
  public boolean isLoadedProperty(int propertyIndex) {
    return loadedProps[propertyIndex];
  }

  /**
   * Return true if the property is considered changed.
   */
  public boolean isChangedProperty(int propertyIndex) {
    return (changedProps != null && changedProps[propertyIndex]);
  }

  /**
   * Return true if the property was changed or if it is embedded and one of its
   * embedded properties is dirty.
   */
  public boolean isDirtyProperty(int propertyIndex) {
    return (changedProps != null && changedProps[propertyIndex] 
        || embeddedDirty != null && embeddedDirty[propertyIndex]);
  }

  /**
   * Explicitly mark a property as having been changed.
   */
  public void markPropertyAsChanged(int propertyIndex) {
    setChangedProperty(propertyIndex);
    setDirty(true);
  }
  
  public void setChangedProperty(int propertyIndex) {
    if (changedProps == null) {
      changedProps = new boolean[owner._ebean_getPropertyNames().length];
    }
    changedProps[propertyIndex] = true;
  }

  /**
   * Set that an embedded bean has had one of its properties changed.
   */
  private void setEmbeddedPropertyDirty(int propertyIndex) {
    if (embeddedDirty == null) {
      embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
    }
    embeddedDirty[propertyIndex] = true;
  }
  
  private void setOriginalValue(int propertyIndex, Object value) {
    if (origValues == null) {
      origValues = new Object[owner._ebean_getPropertyNames().length];
    }
    if (origValues[propertyIndex] == null) {
      origValues[propertyIndex] = value;
    }
  }

  /**
   * For forced update on a 'New' bean set all the loaded properties to changed.
   */
  public void setNewBeanForUpdate() {
  
    if (changedProps == null) {
      changedProps = new boolean[owner._ebean_getPropertyNames().length];
    }
    
    for (int i=0; i< loadedProps.length; i++) {
      if (loadedProps[i]) {
        changedProps[i] = true;        
      }
    }
    setDirty(true);
  }
  
  /**
   * Return the set of property names for a partially loaded bean.
   */
  public Set<String> getLoadedPropertyNames() {
    if (fullyLoadedBean) {
      return null;
    }
    Set<String> props = new LinkedHashSet<String>();
    for (int i=0; i<loadedProps.length; i++) {
      if (loadedProps[i]) {
        props.add(getProperty(i));
      }
    }
    return props;
  }

  /**
   * Return the array of flags indicating the dirty properties.
   */
  public boolean[] getDirtyProperties() {
    int len = getPropertyLength();
    boolean[] dirties = new boolean[len];
    for (int i = 0; i < len; i++) {
      if (changedProps != null && changedProps[i]) {
        dirties[i] = true;
      } else if (embeddedDirty != null && embeddedDirty[i]) {
        // an embedded property has been changed - recurse
        dirties[i] = true;
      }
    }
    return dirties;
  }

  /**
   * Return the set of dirty properties.
   */
  public Set<String> getDirtyPropertyNames() {
    Set<String> props = new LinkedHashSet<String>();
    addDirtyPropertyNames(props, null);
    return props;
  }
  
  /**
   * Recursively add dirty properties.
   */
  public void addDirtyPropertyNames(Set<String> props, String prefix) {
    int len = getPropertyLength();
    for (int i = 0; i < len; i++) {
      if (changedProps != null && changedProps[i]) {
        // the property has been changed on this bean
        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
        props.add(propName);
      } else if (embeddedDirty != null && embeddedDirty[i]) {
        // an embedded property has been changed - recurse
        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
        embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i)+".");
      }
    }
  }

  /**
   * Return true if any of the given property names are dirty.
   */
  public boolean hasDirtyProperty(Set<String> propertyNames) {

    String[] names = owner._ebean_getPropertyNames();
    int len = getPropertyLength();
    for (int i = 0; i < len; i++) {
      if (changedProps != null && changedProps[i]) {
        // the property has been changed on this bean
        if (propertyNames.contains(names[i])) {
          return true;
        }
      } else if (embeddedDirty != null && embeddedDirty[i]) {
        if (propertyNames.contains(names[i])) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Return a map of dirty properties with their new and old values.
   */
  public Map<String,ValuePair> getDirtyValues() {
    Map<String,ValuePair> dirtyValues = new LinkedHashMap<String, ValuePair>();
    addDirtyPropertyValues(dirtyValues, null);
    return dirtyValues;
  }
  
  /**
   * Recursively add dirty properties.
   */
  public void addDirtyPropertyValues(Map<String,ValuePair> dirtyValues, String prefix) {
    int len = getPropertyLength();
    for (int i = 0; i < len; i++) {
      if (changedProps != null && changedProps[i]) {
        // the property has been changed on this bean
        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
        Object newVal = owner._ebean_getField(i);
        Object oldVal = getOrigValue(i);

        dirtyValues.put(propName, new ValuePair(newVal, oldVal));
        
      } else if (embeddedDirty != null && embeddedDirty[i]) {
        // an embedded property has been changed - recurse
        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
        embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
      }
    }
  }
  
  /**
   * Return a dirty property hash taking into account embedded beans.
   */
  public int getDirtyPropertyHash() {
    return addDirtyPropertyHash(37);
  }
  
  /**
   * Add and return a dirty property hash recursing into embedded beans.
   */
  public int addDirtyPropertyHash(int hash) {
    int len = getPropertyLength();
    for (int i = 0; i < len; i++) {
      if (changedProps != null && changedProps[i]) {
        // the property has been changed on this bean
        hash = hash * 31 + (i+1);
      } else if (embeddedDirty != null && embeddedDirty[i]) {
        // an embedded property has been changed - recurse
        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
        hash = hash * 31 + embeddedBean._ebean_getIntercept().addDirtyPropertyHash(hash);
      }
    }
    return hash;
  }

  /**
   * Return a loaded property hash.
   */
  public int getLoadedPropertyHash() {
    int hash = 37;
    int len = getPropertyLength();
    for (int i = 0; i < len; i++) {
      if (isLoadedProperty(i)) {
        hash = hash * 31 + (i+1);
      }
    }
    return hash;
  }

  /**
   * Return the set of property names for changed properties.
   */
  public boolean[] getChanged() {
    return changedProps;
  }

  public boolean[] getLoaded() {
    return loadedProps;
  }

  /**
   * Return the index of the property that triggered the lazy load.
   */
  public int getLazyLoadPropertyIndex() {
    return lazyLoadProperty;
  }
  
  /**
   * Return the property that triggered the lazy load.
   */
  public String getLazyLoadProperty() {
    return getProperty(lazyLoadProperty);
  }

  /**
   * Load the bean when it is a reference.
   */
  protected void loadBean(int loadProperty) {

    synchronized (this) {
      if (beanLoader == null) {
        BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName);
        if (serverLoader == null) {
          throw new PersistenceException("Server [" + ebeanServerName + "] was not found?");
        }

        // For stand alone reference bean or after deserialisation lazy load
        // using the ebeanServer. Synchronise only on the bean.
        loadBeanInternal(loadProperty, serverLoader);
        return;
      }
    }

    synchronized (beanLoader) {
      // Lazy loading using LoadBeanContext which supports batch loading
      // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
      loadBeanInternal(loadProperty, beanLoader);
    }
  }

  /**
   * Invoke the lazy loading. This method is synchronised externally.
   */
  private void loadBeanInternal(int loadProperty, BeanLoader loader) {

    if (loadedProps == null || loadedProps[loadProperty]) {
      // race condition where multiple threads calling preGetter concurrently
      return;
    }

    if (lazyLoadFailure) {
      // failed when batch lazy loaded by another bean in the batch
      throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
    }

    if (lazyLoadProperty == -1) {

      lazyLoadProperty = loadProperty;

      if (nodeUsageCollector != null) {
        nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty));
      }

      loader.loadBean(this);

      if (lazyLoadFailure) {
        // failed when lazy loading this bean
        throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
      }

      // bean should be loaded and intercepting now. setLoaded() has
      // been called by the lazy loading mechanism
    }
  }

  /**
   * Helper method to check if two objects are equal.
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  protected boolean areEqual(Object obj1, Object obj2) {
    if (obj1 == null) {
      return (obj2 == null);
    }
    if (obj2 == null) {
      return false;
    }
    if (obj1 == obj2) {
      return true;
    }
    if (obj1 instanceof BigDecimal) {
      // Use comparable for BigDecimal as equals
      // uses scale in comparison...
      if (obj2 instanceof BigDecimal) {
        Comparable com1 = (Comparable) obj1;
        return (com1.compareTo(obj2) == 0);

      } else {
        return false;
      }
    }
    if (obj1 instanceof URL) {
      // use the string format to determine if dirty
      return obj1.toString().equals(obj2.toString());
    }
    return obj1.equals(obj2);
  }
  
  /**
   * Called when a BeanCollection is initialised automatically.
   */
  public void initialisedMany(int propertyIndex) {
    loadedProps[propertyIndex] = true;
  }

  private final void preGetterCallback() {
    if (preGetterCallback != null) {
      preGetterCallback.preGetterTrigger();
    }
  }

  /**
   * Called prior to Id property getter.
   */
  public void preGetId() {
    preGetterCallback();
  }

  /**
   * Method that is called prior to a getter method on the actual entity.
   */
  public void preGetter(int propertyIndex) {
    preGetterCallback();
    if (state == STATE_NEW || disableLazyLoad) {
      return;
    }
    if (!isLoadedProperty(propertyIndex)) {
      loadBean(propertyIndex);
    }
    if (nodeUsageCollector != null) {
      nodeUsageCollector.addUsed(getProperty(propertyIndex));
    }
  }

  /**
   * Called for "enhancement" postSetter processing. This is around a PUTFIELD
   * so no need to check the newValue afterwards.
   */
  public void postSetter(PropertyChangeEvent event) {
    if (pcs != null && event != null) {
      pcs.firePropertyChange(event);
    }
  }

  /**
   * Called for "subclassed" postSetter processing. Here the newValue has to be
   * re-fetched (and passed into this method) in case there is code inside the
   * setter that further mutates the value.
   */
  public void postSetter(PropertyChangeEvent event, Object newValue) {
    if (pcs != null && event != null) {
      if (newValue != null && newValue.equals(event.getNewValue())) {
        pcs.firePropertyChange(event);
      } else {
        pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
      }
    }
  }

  /**
   * OneToMany and ManyToMany don't have any interception so just check for
   * PropertyChangeSupport.
   */
  public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {

    if (readOnly) {
      throw new IllegalStateException("This bean is readOnly");
    }
    
    setLoadedProperty(propertyIndex);

    // Bean itself not considered dirty when many changed
    if (pcs != null) {
      return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
    } else {
      return null;
    }
  }
  
  private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {

    if (readOnly) {
      throw new IllegalStateException("This bean is readOnly");
    }
    setChangedProperty(propertyIndex);

    if (setDirtyState) {
      setOriginalValue(propertyIndex, origValue);
      if (!dirty) {
        dirty = true;        
        if (embeddedOwner != null) {
          // Cascade dirty state from Embedded bean to parent bean
          embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
        }
        if (nodeUsageCollector != null) {
          nodeUsageCollector.setModified();
        }
      }
    }
  }
  
  /**
   * Check to see if the values are not equal. If they are not equal then create
   * the old values for use with ConcurrencyMode.ALL.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (!areEqual(oldValue, newValue)) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);   
    } else {
      return null;
    }
    
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); 
  }
  
  
  /**
   * Check for primitive boolean.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * Check for primitive int.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * long.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);  
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * double.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);  
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * float.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null :  new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * short.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * char.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * byte.
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (oldValue != newValue) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * char[].
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (!areEqualChars(oldValue, newValue)) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null: new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  /**
   * byte[].
   */
  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {

    if (state == STATE_NEW) {
      setLoadedProperty(propertyIndex);
    } else if (!areEqualBytes(oldValue, newValue)) {
      setChangedPropertyValue(propertyIndex, intercept, oldValue);
    } else {
      return null;
    }
    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
  }

  private static boolean areEqualBytes(byte[] b1, byte[] b2) {
    if (b1 == null) {
      return (b2 == null);

    } else if (b2 == null) {
      return false;

    } else if (b1 == b2) {
      return true;

    } else if (b1.length != b2.length) {
      return false;
    }
    for (int i = 0; i < b1.length; i++) {
      if (b1[i] != b2[i]) {
        return false;
      }
    }
    return true;
  }

  private static boolean areEqualChars(char[] b1, char[] b2) {
    if (b1 == null) {
      return (b2 == null);

    } else if (b2 == null) {
      return false;

    } else if (b1 == b2) {
      return true;

    } else if (b1.length != b2.length) {
      return false;
    }
    for (int i = 0; i < b1.length; i++) {
      if (b1[i] != b2[i]) {
        return false;
      }
    }
    return true;
  }
}
