001package com.avaje.ebean.bean;
002
003import com.avaje.ebean.Ebean;
004import com.avaje.ebean.ValuePair;
005
006import javax.persistence.EntityNotFoundException;
007import javax.persistence.PersistenceException;
008import java.beans.PropertyChangeEvent;
009import java.beans.PropertyChangeListener;
010import java.beans.PropertyChangeSupport;
011import java.io.Serializable;
012import java.math.BigDecimal;
013import java.net.URL;
014import java.util.LinkedHashMap;
015import java.util.LinkedHashSet;
016import java.util.Map;
017import java.util.Set;
018
019/**
020 * This is the object added to every entity bean using byte code enhancement.
021 * <p>
022 * This provides the mechanisms to support deferred fetching of reference beans
023 * and oldValues generation for concurrency checking.
024 * </p>
025 */
026public final class EntityBeanIntercept implements Serializable {
027
028  private static final long serialVersionUID = -3664031775464862649L;
029
030  private static final int STATE_NEW = 0;
031  private static final int STATE_REFERENCE = 1;
032  private static final int STATE_LOADED = 2;
033  
034  private transient NodeUsageCollector nodeUsageCollector;
035
036  private transient PropertyChangeSupport pcs;
037
038  private transient PersistenceContext persistenceContext;
039
040  private transient BeanLoader beanLoader;
041
042  private String ebeanServerName;
043
044  /**
045   * The actual entity bean that 'owns' this intercept.
046   */
047  private final EntityBean owner;
048
049  private EntityBean embeddedOwner;
050  private int embeddedOwnerIndex;
051
052  /**
053   * One of NEW, REF, UPD.
054   */
055  private int state;
056  
057  private boolean readOnly;
058  
059  private boolean dirty;
060  
061  /**
062   * Flag set to disable lazy loading - typically for SQL "report" type entity beans.
063   */
064  private boolean disableLazyLoad;
065
066  /**
067   * Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
068   */
069  private boolean lazyLoadFailure;
070
071  /**
072   * Used when a bean is partially filled.
073   */
074  private final boolean[] loadedProps;
075  
076  private boolean fullyLoadedBean;
077
078  /**
079   * Set of changed properties.
080   */
081  private boolean[] changedProps;
082  
083  /**
084   * Flags indicating if a property is a dirty embedded bean. Used to distingush
085   * between an embedded bean being completely overwritten and one of its
086   * embedded properties being made dirty.
087   */
088  private boolean[] embeddedDirty;
089
090  private Object[] origValues;
091
092  private int lazyLoadProperty = -1;
093
094  private Object ownerId;
095
096  /**
097   * Create a intercept with a given entity.
098   * <p>
099   * Refer to agent ProxyConstructor.
100   * </p>
101   */
102  public EntityBeanIntercept(Object ownerBean) {
103    this.owner = (EntityBean) ownerBean;
104    this.loadedProps = new boolean[owner._ebean_getPropertyNames().length];
105  }
106
107  /**
108   * Return the 'owning' entity bean.
109   */
110  public EntityBean getOwner() {
111    return owner;
112  }
113
114  /**
115   * Return the persistenceContext.
116   */
117  public PersistenceContext getPersistenceContext() {
118    return persistenceContext;
119  }
120
121  /**
122   * Set the persistenceContext.
123   */
124  public void setPersistenceContext(PersistenceContext persistenceContext) {
125    this.persistenceContext = persistenceContext;
126  }
127
128  /**
129   * Add a property change listener for this entity bean.
130   */
131  public void addPropertyChangeListener(PropertyChangeListener listener) {
132    if (pcs == null) {
133      pcs = new PropertyChangeSupport(owner);
134    }
135    pcs.addPropertyChangeListener(listener);
136  }
137
138  /**
139   * Add a property change listener for this entity bean for a specific
140   * property.
141   */
142  public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
143    if (pcs == null) {
144      pcs = new PropertyChangeSupport(owner);
145    }
146    pcs.addPropertyChangeListener(propertyName, listener);
147  }
148
149  /**
150   * Remove a property change listener for this entity bean.
151   */
152  public void removePropertyChangeListener(PropertyChangeListener listener) {
153    if (pcs != null) {
154      pcs.removePropertyChangeListener(listener);
155    }
156  }
157
158  /**
159   * Remove a property change listener for this entity bean for a specific
160   * property.
161   */
162  public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
163    if (pcs != null) {
164      pcs.removePropertyChangeListener(propertyName, listener);
165    }
166  }
167
168  /**
169   * Turn on profile collection.
170   */
171  public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
172    this.nodeUsageCollector = usageCollector;
173  }
174
175  /**
176   * Return the owning bean for an embedded bean.
177   */
178  public Object getEmbeddedOwner() {
179    return embeddedOwner;
180  }
181  
182  /**
183   * Return the property index (for the parent) of this embedded bean.
184   */
185  public int getEmbeddedOwnerIndex() {
186    return embeddedOwnerIndex;
187  }
188
189  /**
190   * Set the embedded beans owning bean.
191   */
192  public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
193    this.embeddedOwner = parentBean;
194    this.embeddedOwnerIndex = embeddedOwnerIndex;
195  }
196
197  /**
198   * Set the BeanLoader with PersistenceContext.
199   */
200  public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
201    this.beanLoader = beanLoader;
202    this.persistenceContext = ctx;
203    this.ebeanServerName = beanLoader.getName();
204  }
205
206  /**
207   * Set the BeanLoader.
208   */
209  public void setBeanLoader(BeanLoader beanLoader) {
210    this.beanLoader = beanLoader;
211    this.ebeanServerName = beanLoader.getName();
212  }
213
214  public boolean isFullyLoadedBean() {
215    return fullyLoadedBean;
216  }
217
218  public void setFullyLoadedBean(boolean fullyLoadedBean) {
219    this.fullyLoadedBean = fullyLoadedBean;
220  }
221
222  /**
223   * Return true if this bean has been directly modified (it has oldValues) or
224   * if any embedded beans are either new or dirty (and hence need saving).
225   */
226  public boolean isDirty() {
227    return dirty;
228  }
229
230  /**
231   * Called by an embedded bean onto its owner.
232   */
233  public void setEmbeddedDirty(int embeddedProperty) {
234    this.dirty = true;
235    setEmbeddedPropertyDirty(embeddedProperty);
236  }
237  
238  public void setDirty(boolean dirty) {
239    this.dirty = dirty;
240  }
241
242  /**
243   * Return true if this entity bean is new and not yet saved.
244   */
245  public boolean isNew() {
246    return state == STATE_NEW;
247  }
248
249  /**
250   * Return true if the entity bean is new or dirty (and should be saved).
251   */
252  public boolean isNewOrDirty() {
253    return isNew() || isDirty();
254  }
255
256  /**
257   * Return true if only the Id property has been loaded.
258   */
259  public boolean hasIdOnly(int idIndex) {
260    for (int i = 0; i < loadedProps.length; i++) {
261      if (i == idIndex) {
262        if (!loadedProps[i]) return false;
263      } else if (loadedProps[i]) {
264        return false; 
265      }
266    }
267    return true;
268  }
269  
270  /**
271   * Return true if the entity is a reference.
272   */
273  public boolean isReference() {
274    return state == STATE_REFERENCE;
275  }
276
277  /**
278   * Set this as a reference object.
279   */
280  public void setReference(int idPos) {
281    state = STATE_REFERENCE;
282    if (idPos > -1) {
283      // For cases where properties are set on constructor
284      // set every non Id property to unloaded (for lazy loading)
285      for (int i=0; i< loadedProps.length; i++) {
286        if (i != idPos) { 
287          loadedProps[i] = false;
288        }
289      }
290    }
291  }
292
293  /**
294   * Return true if the bean should be treated as readOnly. If a setter method
295   * is called when it is readOnly an Exception is thrown.
296   */
297  public boolean isReadOnly() {
298    return readOnly;
299  }
300
301  /**
302   * Set the readOnly status. If readOnly then calls to setter methods through
303   * an exception.
304   */
305  public void setReadOnly(boolean readOnly) {
306    this.readOnly = readOnly;
307  }
308
309  /**
310   * Return true if the entity has been loaded.
311   */
312  public boolean isLoaded() {
313    return state == STATE_LOADED;
314  }
315
316  /**
317   * Set the loaded state to true.
318   * <p>
319   * Calls to setter methods after the bean is loaded can result in 'Old Values'
320   * being created to support ConcurrencyMode.ALL
321   * </p>
322   * <p>
323   * Worth noting that this is also set after a insert/update. By doing so it
324   * 'resets' the bean for making further changes and saving again.
325   * </p>
326   */
327  public void setLoaded() {
328    this.state = STATE_LOADED;
329    this.owner._ebean_setEmbeddedLoaded();
330    this.lazyLoadProperty = -1;
331    this.origValues = null;
332    this.changedProps = null;
333    this.dirty = false;
334  }
335
336  /**
337   * When finished loading for lazy or refresh on an already partially populated
338   * bean.
339   */
340  public void setLoadedLazy() {
341    this.state = STATE_LOADED;
342    this.lazyLoadProperty = -1;
343  }
344
345  /**
346   * Set lazy load failure flag.
347   */
348  public void setLazyLoadFailure(Object ownerId) {
349    this.lazyLoadFailure = true;
350    this.ownerId = ownerId;
351  }
352
353  /**
354   * Return true if the bean is marked as having failed lazy loading.
355   */
356  public boolean isLazyLoadFailure() {
357    return lazyLoadFailure;
358  }
359
360  /**
361   * Return true if lazy loading is disabled.
362   */
363  public boolean isDisableLazyLoad() {
364    return disableLazyLoad;
365  }
366
367  /**
368   * Set true to turn off lazy loading.
369   * <p>
370   * Typically used to disable lazy loading on SQL based report beans.
371   * </p>
372   */
373  public void setDisableLazyLoad(boolean disableLazyLoad) {
374    this.disableLazyLoad = disableLazyLoad;
375  }
376
377  /**
378   * Set the loaded status for the embedded bean.
379   */
380  public void setEmbeddedLoaded(Object embeddedBean) {
381    if (embeddedBean instanceof EntityBean) {
382      EntityBean eb = (EntityBean) embeddedBean;
383      eb._ebean_getIntercept().setLoaded();
384    }
385  }
386
387  /**
388   * Return true if the embedded bean is new or dirty and hence needs saving.
389   */
390  public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
391
392    if (embeddedBean == null) {
393      // if it was previously set then the owning bean would
394      // have oldValues containing the previous embedded bean
395      return false;
396    }
397    if (embeddedBean instanceof EntityBean) {
398      return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();
399
400    } else {
401      // non-enhanced so must assume it is new and needs to be saved
402      return true;
403    }
404  }
405
406  /**
407   * Return the original value that was changed via an update.
408   */
409  public Object getOrigValue(int propertyIndex) {
410    if (origValues == null) {
411      return null;
412    }
413    return origValues[propertyIndex];
414  }
415  
416  /**
417   * Finds the index position of a given property. Returns -1 if the property
418   * can not be found.
419   */
420  public int findProperty(String propertyName) {
421    String[] names = owner._ebean_getPropertyNames();
422    for (int i = 0; i < names.length; i++) {
423      if (names[i].equals(propertyName)) {
424        return i;
425      }
426    }
427    return -1;
428  }
429  
430  /**
431   * Return the property name for the given property.
432   */
433  public String getProperty(int propertyIndex) {
434    if (propertyIndex == -1) {
435      return null;
436    }
437    return owner._ebean_getPropertyName(propertyIndex);
438  }
439  
440  /**
441   * Return the number of properties.s
442   */
443  public int getPropertyLength() {
444    return owner._ebean_getPropertyNames().length;
445  }
446
447  /**
448   * Set the loaded state of the property given it's name.
449   */
450  public void setPropertyLoaded(String propertyName, boolean loaded) {
451    int position = findProperty(propertyName);
452    if (position == -1) {
453      throw new IllegalArgumentException("Property "+propertyName+" not found");
454    }
455    loadedProps[position] = loaded;
456  }
457
458  /**
459   * Set the property to be treated as unloaded. Used for properties initialised in default
460   * constructor.
461   */
462  public void setPropertyUnloaded(int propertyIndex) {
463    loadedProps[propertyIndex] = false;
464  }
465  
466  /**
467   * Set the property to be loaded.
468   */
469  public void setLoadedProperty(int propertyIndex) {
470    loadedProps[propertyIndex] = true;
471  }
472  
473  /**
474   * Return true if the property is loaded.
475   */
476  public boolean isLoadedProperty(int propertyIndex) {
477    return loadedProps[propertyIndex];
478  }
479
480  /**
481   * Return true if the property is considered changed.
482   */
483  public boolean isChangedProperty(int propertyIndex) {
484    return (changedProps != null && changedProps[propertyIndex]);
485  }
486
487  /**
488   * Return true if the property was changed or if it is embedded and one of its
489   * embedded properties is dirty.
490   */
491  public boolean isDirtyProperty(int propertyIndex) {
492    return (changedProps != null && changedProps[propertyIndex] 
493        || embeddedDirty != null && embeddedDirty[propertyIndex]);
494  }
495
496  /**
497   * Explicitly mark a property as having been changed.
498   */
499  public void markPropertyAsChanged(int propertyIndex) {
500    setChangedProperty(propertyIndex);
501    setDirty(true);
502  }
503  
504  public void setChangedProperty(int propertyIndex) {
505    if (changedProps == null) {
506      changedProps = new boolean[owner._ebean_getPropertyNames().length];
507    }
508    changedProps[propertyIndex] = true;
509  }
510
511  /**
512   * Set that an embedded bean has had one of its properties changed.
513   */
514  private void setEmbeddedPropertyDirty(int propertyIndex) {
515    if (embeddedDirty == null) {
516      embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
517    }
518    embeddedDirty[propertyIndex] = true;
519  }
520  
521  private void setOriginalValue(int propertyIndex, Object value) {
522    if (origValues == null) {
523      origValues = new Object[owner._ebean_getPropertyNames().length];
524    }
525    if (origValues[propertyIndex] == null) {
526      origValues[propertyIndex] = value;
527    }
528  }
529
530  /**
531   * For forced update on a 'New' bean set all the loaded properties to changed.
532   */
533  public void setNewBeanForUpdate() {
534  
535    if (changedProps == null) {
536      changedProps = new boolean[owner._ebean_getPropertyNames().length];
537    }
538    
539    for (int i=0; i< loadedProps.length; i++) {
540      if (loadedProps[i]) {
541        changedProps[i] = true;        
542      }
543    }
544    setDirty(true);
545  }
546  
547  /**
548   * Return the set of property names for a partially loaded bean.
549   */
550  public Set<String> getLoadedPropertyNames() {
551    if (fullyLoadedBean) {
552      return null;
553    }
554    Set<String> props = new LinkedHashSet<String>();
555    for (int i=0; i<loadedProps.length; i++) {
556      if (loadedProps[i]) {
557        props.add(getProperty(i));
558      }
559    }
560    return props;
561  }
562
563  /**
564   * Return the set of dirty properties.
565   */
566  public Set<String> getDirtyPropertyNames() {
567    Set<String> props = new LinkedHashSet<String>();
568    addDirtyPropertyNames(props, null);
569    return props;
570  }
571  
572  /**
573   * Recursively add dirty properties.
574   */
575  public void addDirtyPropertyNames(Set<String> props, String prefix) {
576    int len = getPropertyLength();
577    for (int i = 0; i < len; i++) {
578      if (changedProps != null && changedProps[i]) {
579        // the property has been changed on this bean
580        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
581        props.add(propName);
582      } else if (embeddedDirty != null && embeddedDirty[i]) {
583        // an embedded property has been changed - recurse
584        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
585        embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i)+".");
586      }
587    }
588  }
589
590  /**
591   * Return true if any of the given property names are dirty.
592   */
593  public boolean hasDirtyProperty(Set<String> propertyNames) {
594
595    String[] names = owner._ebean_getPropertyNames();
596    int len = getPropertyLength();
597    for (int i = 0; i < len; i++) {
598      if (changedProps != null && changedProps[i]) {
599        // the property has been changed on this bean
600        if (propertyNames.contains(names[i])) {
601          return true;
602        }
603      } else if (embeddedDirty != null && embeddedDirty[i]) {
604        if (propertyNames.contains(names[i])) {
605          return true;
606        }
607      }
608    }
609    return false;
610  }
611
612  /**
613   * Return a map of dirty properties with their new and old values.
614   */
615  public Map<String,ValuePair> getDirtyValues() {
616    Map<String,ValuePair> dirtyValues = new LinkedHashMap<String, ValuePair>();
617    addDirtyPropertyValues(dirtyValues, null);
618    return dirtyValues;
619  }
620  
621  /**
622   * Recursively add dirty properties.
623   */
624  public void addDirtyPropertyValues(Map<String,ValuePair> dirtyValues, String prefix) {
625    int len = getPropertyLength();
626    for (int i = 0; i < len; i++) {
627      if (changedProps != null && changedProps[i]) {
628        // the property has been changed on this bean
629        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
630        Object newVal = owner._ebean_getField(i);
631        Object oldVal = getOrigValue(i);
632
633        dirtyValues.put(propName, new ValuePair(newVal, oldVal));
634        
635      } else if (embeddedDirty != null && embeddedDirty[i]) {
636        // an embedded property has been changed - recurse
637        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
638        embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
639      }
640    }
641  }
642  
643  /**
644   * Return a dirty property hash taking into account embedded beans.
645   */
646  public int getDirtyPropertyHash() {
647    return addDirtyPropertyHash(37);
648  }
649  
650  /**
651   * Add and return a dirty property hash recursing into embedded beans.
652   */
653  public int addDirtyPropertyHash(int hash) {
654    int len = getPropertyLength();
655    for (int i = 0; i < len; i++) {
656      if (changedProps != null && changedProps[i]) {
657        // the property has been changed on this bean
658        hash = hash * 31 + (i+1);
659      } else if (embeddedDirty != null && embeddedDirty[i]) {
660        // an embedded property has been changed - recurse
661        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
662        hash = hash * 31 + embeddedBean._ebean_getIntercept().addDirtyPropertyHash(hash);
663      }
664    }
665    return hash;
666  }
667
668  /**
669   * Return a loaded property hash.
670   */
671  public int getLoadedPropertyHash() {
672    int hash = 37;
673    int len = getPropertyLength();
674    for (int i = 0; i < len; i++) {
675      if (isLoadedProperty(i)) {
676        hash = hash * 31 + (i+1);
677      }
678    }
679    return hash;
680  }
681
682  /**
683   * Return the set of property names for changed properties.
684   */
685  public boolean[] getChanged() {
686    return changedProps;
687  }
688
689  public boolean[] getLoaded() {
690    return loadedProps;
691  }
692
693  /**
694   * Return the index of the property that triggered the lazy load.
695   */
696  public int getLazyLoadPropertyIndex() {
697    return lazyLoadProperty;
698  }
699  
700  /**
701   * Return the property that triggered the lazy load.
702   */
703  public String getLazyLoadProperty() {
704    return getProperty(lazyLoadProperty);
705  }
706
707  /**
708   * Load the bean when it is a reference.
709   */
710  protected void loadBean(int loadProperty) {
711
712    synchronized (this) {
713      if (beanLoader == null) {
714        BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName);
715        if (serverLoader == null) {
716          throw new PersistenceException("Server [" + ebeanServerName + "] was not found?");
717        }
718
719        // For stand alone reference bean or after deserialisation lazy load
720        // using the ebeanServer. Synchronise only on the bean.
721        loadBeanInternal(loadProperty, serverLoader);
722        return;
723      }
724    }
725
726    synchronized (beanLoader) {
727      // Lazy loading using LoadBeanContext which supports batch loading
728      // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
729      loadBeanInternal(loadProperty, beanLoader);
730    }
731  }
732
733  /**
734   * Invoke the lazy loading. This method is synchronised externally.
735   */
736  private void loadBeanInternal(int loadProperty, BeanLoader loader) {
737
738    if (loadedProps == null || loadedProps[loadProperty]) {
739      // race condition where multiple threads calling preGetter concurrently
740      return;
741    }
742
743    if (lazyLoadFailure) {
744      // failed when batch lazy loaded by another bean in the batch
745      throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
746    }
747
748    if (lazyLoadProperty == -1) {
749
750      lazyLoadProperty = loadProperty;
751
752      if (nodeUsageCollector != null) {
753        nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty));
754      }
755
756      loader.loadBean(this);
757
758      if (lazyLoadFailure) {
759        // failed when lazy loading this bean
760        throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
761      }
762
763      // bean should be loaded and intercepting now. setLoaded() has
764      // been called by the lazy loading mechanism
765    }
766  }
767
768  /**
769   * Helper method to check if two objects are equal.
770   */
771  @SuppressWarnings({ "unchecked", "rawtypes" })
772  protected boolean areEqual(Object obj1, Object obj2) {
773    if (obj1 == null) {
774      return (obj2 == null);
775    }
776    if (obj2 == null) {
777      return false;
778    }
779    if (obj1 == obj2) {
780      return true;
781    }
782    if (obj1 instanceof BigDecimal) {
783      // Use comparable for BigDecimal as equals
784      // uses scale in comparison...
785      if (obj2 instanceof BigDecimal) {
786        Comparable com1 = (Comparable) obj1;
787        return (com1.compareTo(obj2) == 0);
788
789      } else {
790        return false;
791      }
792    }
793    if (obj1 instanceof URL) {
794      // use the string format to determine if dirty
795      return obj1.toString().equals(obj2.toString());
796    }
797    return obj1.equals(obj2);
798  }
799  
800  /**
801   * Called when a BeanCollection is initialised automatically.
802   */
803  public void initialisedMany(int propertyIndex) {
804    loadedProps[propertyIndex] = true;
805  }
806  
807  /**
808   * Method that is called prior to a getter method on the actual entity.
809   */
810  public void preGetter(int propertyIndex) {
811    if (state == STATE_NEW || disableLazyLoad) {
812      return;
813    }
814    
815    if (!isLoadedProperty(propertyIndex)) {
816      loadBean(propertyIndex);
817    }
818
819    if (nodeUsageCollector != null) {
820      nodeUsageCollector.addUsed(getProperty(propertyIndex));
821    }
822  }
823
824  /**
825   * Called for "enhancement" postSetter processing. This is around a PUTFIELD
826   * so no need to check the newValue afterwards.
827   */
828  public void postSetter(PropertyChangeEvent event) {
829    if (pcs != null && event != null) {
830      pcs.firePropertyChange(event);
831    }
832  }
833
834  /**
835   * Called for "subclassed" postSetter processing. Here the newValue has to be
836   * re-fetched (and passed into this method) in case there is code inside the
837   * setter that further mutates the value.
838   */
839  public void postSetter(PropertyChangeEvent event, Object newValue) {
840    if (pcs != null && event != null) {
841      if (newValue != null && newValue.equals(event.getNewValue())) {
842        pcs.firePropertyChange(event);
843      } else {
844        pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
845      }
846    }
847  }
848
849  /**
850   * OneToMany and ManyToMany don't have any interception so just check for
851   * PropertyChangeSupport.
852   */
853  public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
854
855    if (readOnly) {
856      throw new IllegalStateException("This bean is readOnly");
857    }
858    
859    setLoadedProperty(propertyIndex);
860
861    // Bean itself not considered dirty when many changed
862    if (pcs != null) {
863      return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
864    } else {
865      return null;
866    }
867  }
868  
869  private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
870
871    if (readOnly) {
872      throw new IllegalStateException("This bean is readOnly");
873    }
874    setChangedProperty(propertyIndex);
875
876    if (setDirtyState) {
877      setOriginalValue(propertyIndex, origValue);
878      if (!dirty) {
879        dirty = true;        
880        if (embeddedOwner != null) {
881          // Cascade dirty state from Embedded bean to parent bean
882          embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
883        }
884        if (nodeUsageCollector != null) {
885          nodeUsageCollector.setModified();
886        }
887      }
888    }
889  }
890  
891  /**
892   * Check to see if the values are not equal. If they are not equal then create
893   * the old values for use with ConcurrencyMode.ALL.
894   */
895  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
896
897    if (state == STATE_NEW) {
898      setLoadedProperty(propertyIndex);
899    } else if (!areEqual(oldValue, newValue)) {
900      setChangedPropertyValue(propertyIndex, intercept, oldValue);   
901    } else {
902      return null;
903    }
904    
905    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); 
906  }
907  
908  
909  /**
910   * Check for primitive boolean.
911   */
912  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
913
914    if (state == STATE_NEW) {
915      setLoadedProperty(propertyIndex);
916    } else if (oldValue != newValue) {
917      setChangedPropertyValue(propertyIndex, intercept, oldValue);
918    } else {
919      return null;
920    }
921    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
922  }
923
924  /**
925   * Check for primitive int.
926   */
927  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
928
929    if (state == STATE_NEW) {
930      setLoadedProperty(propertyIndex);
931    } else if (oldValue != newValue) {
932      setChangedPropertyValue(propertyIndex, intercept, oldValue);
933    } else {
934      return null;
935    }
936    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
937  }
938
939  /**
940   * long.
941   */
942  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
943
944    if (state == STATE_NEW) {
945      setLoadedProperty(propertyIndex);  
946    } else if (oldValue != newValue) {
947      setChangedPropertyValue(propertyIndex, intercept, oldValue);
948    } else {
949      return null;
950    }
951    
952    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
953  }
954
955  /**
956   * double.
957   */
958  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
959
960    if (state == STATE_NEW) {
961      setLoadedProperty(propertyIndex);
962    } else if (oldValue != newValue) {
963      setChangedPropertyValue(propertyIndex, intercept, oldValue);  
964    } else {
965      return null;
966    }
967    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
968  }
969
970  /**
971   * float.
972   */
973  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
974
975    if (state == STATE_NEW) {
976      setLoadedProperty(propertyIndex);
977    } else if (oldValue != newValue) {
978      setChangedPropertyValue(propertyIndex, intercept, oldValue);
979    } else {
980      return null;
981    }
982    return (pcs == null) ? null :  new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
983  }
984
985  /**
986   * short.
987   */
988  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
989
990    if (state == STATE_NEW) {
991      setLoadedProperty(propertyIndex);
992    } else if (oldValue != newValue) {
993      setChangedPropertyValue(propertyIndex, intercept, oldValue);
994    } else {
995      return null;
996    }
997    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
998  }
999
1000  /**
1001   * char.
1002   */
1003  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
1004
1005    if (state == STATE_NEW) {
1006      setLoadedProperty(propertyIndex);
1007    } else if (oldValue != newValue) {
1008      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1009    } else {
1010      return null;
1011    }
1012    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1013  }
1014
1015  /**
1016   * byte.
1017   */
1018  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
1019
1020    if (state == STATE_NEW) {
1021      setLoadedProperty(propertyIndex);
1022    } else if (oldValue != newValue) {
1023      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1024    } else {
1025      return null;
1026    }
1027    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1028  }
1029
1030  /**
1031   * char[].
1032   */
1033  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
1034
1035    if (state == STATE_NEW) {
1036      setLoadedProperty(propertyIndex);
1037    } else if (!areEqualChars(oldValue, newValue)) {
1038      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1039    } else {
1040      return null;
1041    }
1042    return (pcs == null) ? null: new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1043  }
1044
1045  /**
1046   * byte[].
1047   */
1048  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
1049
1050    if (state == STATE_NEW) {
1051      setLoadedProperty(propertyIndex);
1052    } else if (!areEqualBytes(oldValue, newValue)) {
1053      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1054    } else {
1055      return null;
1056    }
1057    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1058  }
1059
1060  private static boolean areEqualBytes(byte[] b1, byte[] b2) {
1061    if (b1 == null) {
1062      return (b2 == null);
1063
1064    } else if (b2 == null) {
1065      return false;
1066
1067    } else if (b1 == b2) {
1068      return true;
1069
1070    } else if (b1.length != b2.length) {
1071      return false;
1072    }
1073    for (int i = 0; i < b1.length; i++) {
1074      if (b1[i] != b2[i]) {
1075        return false;
1076      }
1077    }
1078    return true;
1079  }
1080
1081  private static boolean areEqualChars(char[] b1, char[] b2) {
1082    if (b1 == null) {
1083      return (b2 == null);
1084
1085    } else if (b2 == null) {
1086      return false;
1087
1088    } else if (b1 == b2) {
1089      return true;
1090
1091    } else if (b1.length != b2.length) {
1092      return false;
1093    }
1094    for (int i = 0; i < b1.length; i++) {
1095      if (b1[i] != b2[i]) {
1096        return false;
1097      }
1098    }
1099    return true;
1100  }
1101}