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