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   * Check each property to see if the bean is partially loaded.
224   */
225  public boolean isPartial() {
226    for (int i = 0; i < loadedProps.length; i++) {
227      if (!loadedProps[i]) {
228        return true;
229      }
230    }
231    return false;
232  }
233
234  /**
235   * Return true if this bean has been directly modified (it has oldValues) or
236   * if any embedded beans are either new or dirty (and hence need saving).
237   */
238  public boolean isDirty() {
239    return dirty;
240  }
241
242  /**
243   * Called by an embedded bean onto its owner.
244   */
245  public void setEmbeddedDirty(int embeddedProperty) {
246    this.dirty = true;
247    setEmbeddedPropertyDirty(embeddedProperty);
248  }
249  
250  public void setDirty(boolean dirty) {
251    this.dirty = dirty;
252  }
253
254  /**
255   * Return true if this entity bean is new and not yet saved.
256   */
257  public boolean isNew() {
258    return state == STATE_NEW;
259  }
260
261  /**
262   * Return true if the entity bean is new or dirty (and should be saved).
263   */
264  public boolean isNewOrDirty() {
265    return isNew() || isDirty();
266  }
267
268  /**
269   * Return true if only the Id property has been loaded.
270   */
271  public boolean hasIdOnly(int idIndex) {
272    for (int i = 0; i < loadedProps.length; i++) {
273      if (i == idIndex) {
274        if (!loadedProps[i]) return false;
275      } else if (loadedProps[i]) {
276        return false; 
277      }
278    }
279    return true;
280  }
281  
282  /**
283   * Return true if the entity is a reference.
284   */
285  public boolean isReference() {
286    return state == STATE_REFERENCE;
287  }
288
289  /**
290   * Set this as a reference object.
291   */
292  public void setReference(int idPos) {
293    state = STATE_REFERENCE;
294    if (idPos > -1) {
295      // For cases where properties are set on constructor
296      // set every non Id property to unloaded (for lazy loading)
297      for (int i=0; i< loadedProps.length; i++) {
298        if (i != idPos) { 
299          loadedProps[i] = false;
300        }
301      }
302    }
303  }
304
305  /**
306   * Return true if the bean should be treated as readOnly. If a setter method
307   * is called when it is readOnly an Exception is thrown.
308   */
309  public boolean isReadOnly() {
310    return readOnly;
311  }
312
313  /**
314   * Set the readOnly status. If readOnly then calls to setter methods through
315   * an exception.
316   */
317  public void setReadOnly(boolean readOnly) {
318    this.readOnly = readOnly;
319  }
320
321  /**
322   * Return true if the entity has been loaded.
323   */
324  public boolean isLoaded() {
325    return state == STATE_LOADED;
326  }
327
328  /**
329   * Set the loaded state to true.
330   * <p>
331   * Calls to setter methods after the bean is loaded can result in 'Old Values'
332   * being created to support ConcurrencyMode.ALL
333   * </p>
334   * <p>
335   * Worth noting that this is also set after a insert/update. By doing so it
336   * 'resets' the bean for making further changes and saving again.
337   * </p>
338   */
339  public void setLoaded() {
340    this.state = STATE_LOADED;
341    this.owner._ebean_setEmbeddedLoaded();
342    this.lazyLoadProperty = -1;
343    this.origValues = null;
344    this.changedProps = null;
345    this.dirty = false;
346  }
347
348  /**
349   * When finished loading for lazy or refresh on an already partially populated
350   * bean.
351   */
352  public void setLoadedLazy() {
353    this.state = STATE_LOADED;
354    this.lazyLoadProperty = -1;
355  }
356
357  /**
358   * Set lazy load failure flag.
359   */
360  public void setLazyLoadFailure(Object ownerId) {
361    this.lazyLoadFailure = true;
362    this.ownerId = ownerId;
363  }
364
365  /**
366   * Return true if the bean is marked as having failed lazy loading.
367   */
368  public boolean isLazyLoadFailure() {
369    return lazyLoadFailure;
370  }
371
372  /**
373   * Return true if lazy loading is disabled.
374   */
375  public boolean isDisableLazyLoad() {
376    return disableLazyLoad;
377  }
378
379  /**
380   * Set true to turn off lazy loading.
381   * <p>
382   * Typically used to disable lazy loading on SQL based report beans.
383   * </p>
384   */
385  public void setDisableLazyLoad(boolean disableLazyLoad) {
386    this.disableLazyLoad = disableLazyLoad;
387  }
388
389  /**
390   * Set the loaded status for the embedded bean.
391   */
392  public void setEmbeddedLoaded(Object embeddedBean) {
393    if (embeddedBean instanceof EntityBean) {
394      EntityBean eb = (EntityBean) embeddedBean;
395      eb._ebean_getIntercept().setLoaded();
396    }
397  }
398
399  /**
400   * Return true if the embedded bean is new or dirty and hence needs saving.
401   */
402  public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
403
404    if (embeddedBean == null) {
405      // if it was previously set then the owning bean would
406      // have oldValues containing the previous embedded bean
407      return false;
408    }
409    if (embeddedBean instanceof EntityBean) {
410      return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();
411
412    } else {
413      // non-enhanced so must assume it is new and needs to be saved
414      return true;
415    }
416  }
417
418  /**
419   * Return the original value that was changed via an update.
420   */
421  public Object getOrigValue(int propertyIndex) {
422    if (origValues == null) {
423      return null;
424    }
425    return origValues[propertyIndex];
426  }
427  
428  /**
429   * Finds the index position of a given property. Returns -1 if the property
430   * can not be found.
431   */
432  public int findProperty(String propertyName) {
433    String[] names = owner._ebean_getPropertyNames();
434    for (int i = 0; i < names.length; i++) {
435      if (names[i].equals(propertyName)) {
436        return i;
437      }
438    }
439    return -1;
440  }
441  
442  /**
443   * Return the property name for the given property.
444   */
445  public String getProperty(int propertyIndex) {
446    if (propertyIndex == -1) {
447      return null;
448    }
449    return owner._ebean_getPropertyName(propertyIndex);
450  }
451  
452  /**
453   * Return the number of properties.s
454   */
455  public int getPropertyLength() {
456    return owner._ebean_getPropertyNames().length;
457  }
458
459  /**
460   * Set the loaded state of the property given it's name.
461   */
462  public void setPropertyLoaded(String propertyName, boolean loaded) {
463    int position = findProperty(propertyName);
464    if (position == -1) {
465      throw new IllegalArgumentException("Property "+propertyName+" not found");
466    }
467    loadedProps[position] = loaded;
468  }
469
470  /**
471   * Set the property to be treated as unloaded. Used for properties initialised in default
472   * constructor.
473   */
474  public void setPropertyUnloaded(int propertyIndex) {
475    loadedProps[propertyIndex] = false;
476  }
477  
478  /**
479   * Set the property to be loaded.
480   */
481  public void setLoadedProperty(int propertyIndex) {
482    loadedProps[propertyIndex] = true;
483  }
484
485  /**
486   * Return true if the property is loaded.
487   */
488  public boolean isLoadedProperty(int propertyIndex) {
489    return loadedProps[propertyIndex];
490  }
491
492  /**
493   * Return true if the property is considered changed.
494   */
495  public boolean isChangedProperty(int propertyIndex) {
496    return (changedProps != null && changedProps[propertyIndex]);
497  }
498
499  /**
500   * Return true if the property was changed or if it is embedded and one of its
501   * embedded properties is dirty.
502   */
503  public boolean isDirtyProperty(int propertyIndex) {
504    return (changedProps != null && changedProps[propertyIndex] 
505        || embeddedDirty != null && embeddedDirty[propertyIndex]);
506  }
507
508  /**
509   * Explicitly mark a property as having been changed.
510   */
511  public void markPropertyAsChanged(int propertyIndex) {
512    setChangedProperty(propertyIndex);
513    setDirty(true);
514  }
515  
516  public void setChangedProperty(int propertyIndex) {
517    if (changedProps == null) {
518      changedProps = new boolean[owner._ebean_getPropertyNames().length];
519    }
520    changedProps[propertyIndex] = true;
521  }
522
523  /**
524   * Set that an embedded bean has had one of its properties changed.
525   */
526  private void setEmbeddedPropertyDirty(int propertyIndex) {
527    if (embeddedDirty == null) {
528      embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
529    }
530    embeddedDirty[propertyIndex] = true;
531  }
532  
533  private void setOriginalValue(int propertyIndex, Object value) {
534    if (origValues == null) {
535      origValues = new Object[owner._ebean_getPropertyNames().length];
536    }
537    if (origValues[propertyIndex] == null) {
538      origValues[propertyIndex] = value;
539    }
540  }
541
542  /**
543   * For forced update on a 'New' bean set all the loaded properties to changed.
544   */
545  public void setNewBeanForUpdate() {
546  
547    if (changedProps == null) {
548      changedProps = new boolean[owner._ebean_getPropertyNames().length];
549    }
550    
551    for (int i=0; i< loadedProps.length; i++) {
552      if (loadedProps[i]) {
553        changedProps[i] = true;        
554      }
555    }
556    setDirty(true);
557  }
558  
559  /**
560   * Return the set of property names for a partially loaded bean.
561   */
562  public Set<String> getLoadedPropertyNames() {
563    if (fullyLoadedBean) {
564      return null;
565    }
566    Set<String> props = new LinkedHashSet<String>();
567    for (int i=0; i<loadedProps.length; i++) {
568      if (loadedProps[i]) {
569        props.add(getProperty(i));
570      }
571    }
572    return props;
573  }
574
575  /**
576   * Return the array of flags indicating the dirty properties.
577   */
578  public boolean[] getDirtyProperties() {
579    int len = getPropertyLength();
580    boolean[] dirties = new boolean[len];
581    for (int i = 0; i < len; i++) {
582      if (changedProps != null && changedProps[i]) {
583        dirties[i] = true;
584      } else if (embeddedDirty != null && embeddedDirty[i]) {
585        // an embedded property has been changed - recurse
586        dirties[i] = true;
587      }
588    }
589    return dirties;
590  }
591
592  /**
593   * Return the set of dirty properties.
594   */
595  public Set<String> getDirtyPropertyNames() {
596    Set<String> props = new LinkedHashSet<String>();
597    addDirtyPropertyNames(props, null);
598    return props;
599  }
600  
601  /**
602   * Recursively add dirty properties.
603   */
604  public void addDirtyPropertyNames(Set<String> props, String prefix) {
605    int len = getPropertyLength();
606    for (int i = 0; i < len; i++) {
607      if (changedProps != null && changedProps[i]) {
608        // the property has been changed on this bean
609        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
610        props.add(propName);
611      } else if (embeddedDirty != null && embeddedDirty[i]) {
612        // an embedded property has been changed - recurse
613        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
614        embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i)+".");
615      }
616    }
617  }
618
619  /**
620   * Return true if any of the given property names are dirty.
621   */
622  public boolean hasDirtyProperty(Set<String> propertyNames) {
623
624    String[] names = owner._ebean_getPropertyNames();
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        if (propertyNames.contains(names[i])) {
630          return true;
631        }
632      } else if (embeddedDirty != null && embeddedDirty[i]) {
633        if (propertyNames.contains(names[i])) {
634          return true;
635        }
636      }
637    }
638    return false;
639  }
640
641  /**
642   * Return a map of dirty properties with their new and old values.
643   */
644  public Map<String,ValuePair> getDirtyValues() {
645    Map<String,ValuePair> dirtyValues = new LinkedHashMap<String, ValuePair>();
646    addDirtyPropertyValues(dirtyValues, null);
647    return dirtyValues;
648  }
649  
650  /**
651   * Recursively add dirty properties.
652   */
653  public void addDirtyPropertyValues(Map<String,ValuePair> dirtyValues, String prefix) {
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        String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
659        Object newVal = owner._ebean_getField(i);
660        Object oldVal = getOrigValue(i);
661
662        dirtyValues.put(propName, new ValuePair(newVal, oldVal));
663        
664      } else if (embeddedDirty != null && embeddedDirty[i]) {
665        // an embedded property has been changed - recurse
666        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
667        embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
668      }
669    }
670  }
671  
672  /**
673   * Return a dirty property hash taking into account embedded beans.
674   */
675  public int getDirtyPropertyHash() {
676    return addDirtyPropertyHash(37);
677  }
678  
679  /**
680   * Add and return a dirty property hash recursing into embedded beans.
681   */
682  public int addDirtyPropertyHash(int hash) {
683    int len = getPropertyLength();
684    for (int i = 0; i < len; i++) {
685      if (changedProps != null && changedProps[i]) {
686        // the property has been changed on this bean
687        hash = hash * 31 + (i+1);
688      } else if (embeddedDirty != null && embeddedDirty[i]) {
689        // an embedded property has been changed - recurse
690        EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
691        hash = hash * 31 + embeddedBean._ebean_getIntercept().addDirtyPropertyHash(hash);
692      }
693    }
694    return hash;
695  }
696
697  /**
698   * Return a loaded property hash.
699   */
700  public int getLoadedPropertyHash() {
701    int hash = 37;
702    int len = getPropertyLength();
703    for (int i = 0; i < len; i++) {
704      if (isLoadedProperty(i)) {
705        hash = hash * 31 + (i+1);
706      }
707    }
708    return hash;
709  }
710
711  /**
712   * Return the set of property names for changed properties.
713   */
714  public boolean[] getChanged() {
715    return changedProps;
716  }
717
718  public boolean[] getLoaded() {
719    return loadedProps;
720  }
721
722  /**
723   * Return the index of the property that triggered the lazy load.
724   */
725  public int getLazyLoadPropertyIndex() {
726    return lazyLoadProperty;
727  }
728  
729  /**
730   * Return the property that triggered the lazy load.
731   */
732  public String getLazyLoadProperty() {
733    return getProperty(lazyLoadProperty);
734  }
735
736  /**
737   * Load the bean when it is a reference.
738   */
739  protected void loadBean(int loadProperty) {
740
741    synchronized (this) {
742      if (beanLoader == null) {
743        BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName);
744        if (serverLoader == null) {
745          throw new PersistenceException("Server [" + ebeanServerName + "] was not found?");
746        }
747
748        // For stand alone reference bean or after deserialisation lazy load
749        // using the ebeanServer. Synchronise only on the bean.
750        loadBeanInternal(loadProperty, serverLoader);
751        return;
752      }
753    }
754
755    synchronized (beanLoader) {
756      // Lazy loading using LoadBeanContext which supports batch loading
757      // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
758      loadBeanInternal(loadProperty, beanLoader);
759    }
760  }
761
762  /**
763   * Invoke the lazy loading. This method is synchronised externally.
764   */
765  private void loadBeanInternal(int loadProperty, BeanLoader loader) {
766
767    if (loadedProps == null || loadedProps[loadProperty]) {
768      // race condition where multiple threads calling preGetter concurrently
769      return;
770    }
771
772    if (lazyLoadFailure) {
773      // failed when batch lazy loaded by another bean in the batch
774      throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
775    }
776
777    if (lazyLoadProperty == -1) {
778
779      lazyLoadProperty = loadProperty;
780
781      if (nodeUsageCollector != null) {
782        nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty));
783      }
784
785      loader.loadBean(this);
786
787      if (lazyLoadFailure) {
788        // failed when lazy loading this bean
789        throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
790      }
791
792      // bean should be loaded and intercepting now. setLoaded() has
793      // been called by the lazy loading mechanism
794    }
795  }
796
797  /**
798   * Helper method to check if two objects are equal.
799   */
800  @SuppressWarnings({ "unchecked", "rawtypes" })
801  protected boolean areEqual(Object obj1, Object obj2) {
802    if (obj1 == null) {
803      return (obj2 == null);
804    }
805    if (obj2 == null) {
806      return false;
807    }
808    if (obj1 == obj2) {
809      return true;
810    }
811    if (obj1 instanceof BigDecimal) {
812      // Use comparable for BigDecimal as equals
813      // uses scale in comparison...
814      if (obj2 instanceof BigDecimal) {
815        Comparable com1 = (Comparable) obj1;
816        return (com1.compareTo(obj2) == 0);
817
818      } else {
819        return false;
820      }
821    }
822    if (obj1 instanceof URL) {
823      // use the string format to determine if dirty
824      return obj1.toString().equals(obj2.toString());
825    }
826    return obj1.equals(obj2);
827  }
828  
829  /**
830   * Called when a BeanCollection is initialised automatically.
831   */
832  public void initialisedMany(int propertyIndex) {
833    loadedProps[propertyIndex] = true;
834  }
835  
836  /**
837   * Method that is called prior to a getter method on the actual entity.
838   */
839  public void preGetter(int propertyIndex) {
840    if (state == STATE_NEW || disableLazyLoad) {
841      return;
842    }
843    
844    if (!isLoadedProperty(propertyIndex)) {
845      loadBean(propertyIndex);
846    }
847
848    if (nodeUsageCollector != null) {
849      nodeUsageCollector.addUsed(getProperty(propertyIndex));
850    }
851  }
852
853  /**
854   * Called for "enhancement" postSetter processing. This is around a PUTFIELD
855   * so no need to check the newValue afterwards.
856   */
857  public void postSetter(PropertyChangeEvent event) {
858    if (pcs != null && event != null) {
859      pcs.firePropertyChange(event);
860    }
861  }
862
863  /**
864   * Called for "subclassed" postSetter processing. Here the newValue has to be
865   * re-fetched (and passed into this method) in case there is code inside the
866   * setter that further mutates the value.
867   */
868  public void postSetter(PropertyChangeEvent event, Object newValue) {
869    if (pcs != null && event != null) {
870      if (newValue != null && newValue.equals(event.getNewValue())) {
871        pcs.firePropertyChange(event);
872      } else {
873        pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
874      }
875    }
876  }
877
878  /**
879   * OneToMany and ManyToMany don't have any interception so just check for
880   * PropertyChangeSupport.
881   */
882  public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
883
884    if (readOnly) {
885      throw new IllegalStateException("This bean is readOnly");
886    }
887    
888    setLoadedProperty(propertyIndex);
889
890    // Bean itself not considered dirty when many changed
891    if (pcs != null) {
892      return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
893    } else {
894      return null;
895    }
896  }
897  
898  private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
899
900    if (readOnly) {
901      throw new IllegalStateException("This bean is readOnly");
902    }
903    setChangedProperty(propertyIndex);
904
905    if (setDirtyState) {
906      setOriginalValue(propertyIndex, origValue);
907      if (!dirty) {
908        dirty = true;        
909        if (embeddedOwner != null) {
910          // Cascade dirty state from Embedded bean to parent bean
911          embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
912        }
913        if (nodeUsageCollector != null) {
914          nodeUsageCollector.setModified();
915        }
916      }
917    }
918  }
919  
920  /**
921   * Check to see if the values are not equal. If they are not equal then create
922   * the old values for use with ConcurrencyMode.ALL.
923   */
924  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
925
926    if (state == STATE_NEW) {
927      setLoadedProperty(propertyIndex);
928    } else if (!areEqual(oldValue, newValue)) {
929      setChangedPropertyValue(propertyIndex, intercept, oldValue);   
930    } else {
931      return null;
932    }
933    
934    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); 
935  }
936  
937  
938  /**
939   * Check for primitive boolean.
940   */
941  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
942
943    if (state == STATE_NEW) {
944      setLoadedProperty(propertyIndex);
945    } else if (oldValue != newValue) {
946      setChangedPropertyValue(propertyIndex, intercept, oldValue);
947    } else {
948      return null;
949    }
950    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
951  }
952
953  /**
954   * Check for primitive int.
955   */
956  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
957
958    if (state == STATE_NEW) {
959      setLoadedProperty(propertyIndex);
960    } else if (oldValue != newValue) {
961      setChangedPropertyValue(propertyIndex, intercept, oldValue);
962    } else {
963      return null;
964    }
965    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
966  }
967
968  /**
969   * long.
970   */
971  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
972
973    if (state == STATE_NEW) {
974      setLoadedProperty(propertyIndex);  
975    } else if (oldValue != newValue) {
976      setChangedPropertyValue(propertyIndex, intercept, oldValue);
977    } else {
978      return null;
979    }
980    
981    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
982  }
983
984  /**
985   * double.
986   */
987  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
988
989    if (state == STATE_NEW) {
990      setLoadedProperty(propertyIndex);
991    } else if (oldValue != newValue) {
992      setChangedPropertyValue(propertyIndex, intercept, oldValue);  
993    } else {
994      return null;
995    }
996    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
997  }
998
999  /**
1000   * float.
1001   */
1002  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
1003
1004    if (state == STATE_NEW) {
1005      setLoadedProperty(propertyIndex);
1006    } else if (oldValue != newValue) {
1007      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1008    } else {
1009      return null;
1010    }
1011    return (pcs == null) ? null :  new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1012  }
1013
1014  /**
1015   * short.
1016   */
1017  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
1018
1019    if (state == STATE_NEW) {
1020      setLoadedProperty(propertyIndex);
1021    } else if (oldValue != newValue) {
1022      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1023    } else {
1024      return null;
1025    }
1026    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1027  }
1028
1029  /**
1030   * char.
1031   */
1032  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
1033
1034    if (state == STATE_NEW) {
1035      setLoadedProperty(propertyIndex);
1036    } else if (oldValue != newValue) {
1037      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1038    } else {
1039      return null;
1040    }
1041    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1042  }
1043
1044  /**
1045   * byte.
1046   */
1047  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
1048
1049    if (state == STATE_NEW) {
1050      setLoadedProperty(propertyIndex);
1051    } else if (oldValue != newValue) {
1052      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1053    } else {
1054      return null;
1055    }
1056    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1057  }
1058
1059  /**
1060   * char[].
1061   */
1062  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
1063
1064    if (state == STATE_NEW) {
1065      setLoadedProperty(propertyIndex);
1066    } else if (!areEqualChars(oldValue, newValue)) {
1067      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1068    } else {
1069      return null;
1070    }
1071    return (pcs == null) ? null: new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1072  }
1073
1074  /**
1075   * byte[].
1076   */
1077  public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
1078
1079    if (state == STATE_NEW) {
1080      setLoadedProperty(propertyIndex);
1081    } else if (!areEqualBytes(oldValue, newValue)) {
1082      setChangedPropertyValue(propertyIndex, intercept, oldValue);
1083    } else {
1084      return null;
1085    }
1086    return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
1087  }
1088
1089  private static boolean areEqualBytes(byte[] b1, byte[] b2) {
1090    if (b1 == null) {
1091      return (b2 == null);
1092
1093    } else if (b2 == null) {
1094      return false;
1095
1096    } else if (b1 == b2) {
1097      return true;
1098
1099    } else if (b1.length != b2.length) {
1100      return false;
1101    }
1102    for (int i = 0; i < b1.length; i++) {
1103      if (b1[i] != b2[i]) {
1104        return false;
1105      }
1106    }
1107    return true;
1108  }
1109
1110  private static boolean areEqualChars(char[] b1, char[] b2) {
1111    if (b1 == null) {
1112      return (b2 == null);
1113
1114    } else if (b2 == null) {
1115      return false;
1116
1117    } else if (b1 == b2) {
1118      return true;
1119
1120    } else if (b1.length != b2.length) {
1121      return false;
1122    }
1123    for (int i = 0; i < b1.length; i++) {
1124      if (b1[i] != b2[i]) {
1125        return false;
1126      }
1127    }
1128    return true;
1129  }
1130}