Package com.dmurph.mvc.model

Source Code of com.dmurph.mvc.model.HashModel$ModelProperty

/**
* Created on Jul 13, 2010, 3:41:56 PM
*/
package com.dmurph.mvc.model;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Iterator;

import com.dmurph.mvc.ICloneable;
import com.dmurph.mvc.IDirtyable;
import com.dmurph.mvc.IModel;
import com.dmurph.mvc.IRevertible;
import com.dmurph.mvc.support.AbstractMVCSupport;
import com.dmurph.mvc.support.ISupportable;
import com.dmurph.mvc.support.RevertibleSupport;
import com.dmurph.mvc.support.RevertibleSupport.PropertyWrapper;

/**
* Model that stores all properties in a HashMap, so all {@link IDirtyable}, {@link ICloneable}, and
* {@link IRevertible} functionality is handled internally.<br/>
* <br/>
* This class also will forward all calls to it's members if implement the associated interface. 
* For example, if {@link #revertChanges()} is called, then, after
* reverting any changes to this model, it will call {@link IRevertible#revertChanges()} on any property
* that is {@link IRevertible}.  This can get dangerous if your property tree goes in a loop (you'll
* get infinite calls).  In that case you can override {@link #isDeepMVCEnabled(String)) to return false for
* properties that you don't want any calls forwarded to, or if you want more control, you can override
* {@link #cloneImpl(Object)}, {@link #revertChangesImpl(IRevertible)}, {@link #isDirtyImpl(IDirtyable)},
* or {@link #saveChangesImpl(IRevertible)} to prevent this as well.
*
* @author Daniel Murphy
*
*/
public class HashModel extends AbstractMVCSupport implements IDirtyable, ICloneable, IRevertible, IModel{
  private static final long serialVersionUID = 2L;
 
  private final HashMap<String, ModelProperty> propertyMap = new HashMap<String, ModelProperty>();
  private final RevertibleSupport revertibleSupport;
 
  public enum PropertyType{
    /**
     * Property that can't be set by
     * calling {@link HashModel#setProperty(String, Object)},
     * but can be set by the extending class by calling
     * {@link HashModel#registerProperty(String, PropertyType, Object)}
     * again.
     */
    READ_ONLY,
    /**
     * Property that can be read and written to by the
     * {@link HashModel#getProperty(String)} and
     * {@link HashModel#setProperty(String, Object)}
     * methods.
     */
    READ_WRITE,
    /**
     * Property that, after registration, cannot be set again
     * by either accessing classes or the implementing class.
     * This guarantees that the object returned from
     * {@link HashModel#getProperty(String)} will always be
     * the correct reference.
     */
    FINAL
  }
 
  // for listening to dirty updates from children
  private final PropertyChangeListener childPropertyChangeListener = new PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent argEvt) {
      if(argEvt.getPropertyName().equals(IModel.DIRTY)){
        if(argEvt.getNewValue() == Boolean.TRUE){
          propertyChangeSupport.firePropertyChange(argEvt);
        }
      }
    }
  };
 
  /**
   * Constructs a hash model with an {@link IModel#DIRTY} property.
   */
  public HashModel(){
    revertibleSupport = new RevertibleSupport(propertyChangeSupport, new ISupportable() {
      public void setProperty(String argPropertyName, Object argProperty) {
        HashModel.this.setProperty(argPropertyName, argProperty);
      }
    }, this);
   
    registerProperty(DIRTY, PropertyType.READ_WRITE, false);
  }
 
  /**
   * Constructs a hash model with an {@link IModel#DIRTY} property, and
   * the given properties all with property type of {@link PropertyType#READ_WRITE}.
   * @param argProperties
   */
  public HashModel(String[] argProperties){
    this();
    registerProperty(argProperties, PropertyType.READ_WRITE);
  }
 
  private void addListener(Object argObject){
    if(argObject instanceof IModel){
      ((IModel) argObject).addPropertyChangeListener(childPropertyChangeListener);
    }
  }
 
  private void removeListener(Object argObject){
    if(argObject instanceof IModel){
      ((IModel) argObject).removePropertyChangeListener(childPropertyChangeListener);
    }
  }
 
  /**
   * Register a property
   * @param argKey
   * @param argType
   */
  protected void registerProperty(String argKey, PropertyType argType){
    registerProperty(argKey, argType, null);
  }
 
  /**
   * Register a property with an initial value
   * @param argKey
   * @param argType
   * @param argInitial
   */
  protected synchronized void registerProperty(String argKey, PropertyType argType, Object argInitial){
    ModelProperty mp;
    if(propertyMap.containsKey(argKey)){
      mp = propertyMap.get(argKey);
      if(mp.type == PropertyType.FINAL){
        return;
      }
    }else{
      mp = new ModelProperty();
    }
    mp.type = argType;
    mp.prop = argInitial;
    addListener(mp.prop);
    propertyMap.put(argKey, mp);
  }
 
  /**
   * Register an array of properties all of the same property type
   * @param argKeys
   * @param argType
   */
  protected synchronized void registerProperty(String[] argKeys, PropertyType argType){
    for(String s: argKeys){
      registerProperty(s, argType, null);
    }
  }
 
  /**
   * Sets a property, and will only set the property if it's {@link PropertyType} is
   * {@link PropertyType#READ_WRITE}.  If the property isn't defined, it will be registered
   * and set with the property type of {@link PropertyType#READ_WRITE}.
   * @see #getPropertyType(String)
   * @see com.dmurph.mvc.support.AbstractMVCSupport#setProperty(java.lang.String, java.lang.Object)
   */
  public synchronized Object setProperty(String argKey, Object argProperty){
    if(propertyMap.containsKey(argKey)){
      ModelProperty mp = propertyMap.get(argKey);
      if(mp.type == PropertyType.READ_WRITE){
        if(mp.prop == argProperty){
          return argProperty;
        }
        Object old = mp.prop;
        removeListener(old);
        mp.prop = argProperty;
        addListener(mp.prop);
        firePropertyChange(argKey, old, argProperty);
        if(!argKey.equals(DIRTY)){
          setProperty(DIRTY, true);
        }
        return old;
      }else{
        return mp.prop;
      }
    }else{
      registerProperty(argKey, PropertyType.READ_WRITE, null);
      return setProperty(argKey, argProperty);
    }
  }
   
  /**
   * Get a property
   * @param argKey
   * @return
   */
  public synchronized Object getProperty(String argKey){
    ModelProperty mp = propertyMap.get(argKey);
    if(mp != null){
      return mp.prop;
    }else{
      return null;
    }
  }
 
  /**
   * Get the {@link PropertyType} for a property.
   * @param argKey
   * @return
   */
  public synchronized PropertyType getPropertyType(String argKey){
    ModelProperty mp = propertyMap.get(argKey);
    if(mp != null){
      return mp.type;
    }else{
      return null;
    }
  }
 
  /**
   * Gets the names of all the properties.
   * @return
   */
  public synchronized String[] getPropertyNames(){
    return propertyMap.keySet().toArray(new String[0]);
  }
 
  /**
   * Tells you if this hash model contains the given property.
   * @param argProperty
   * @return
   */
  public synchronized boolean containsProperty(String argProperty){
    return propertyMap.containsKey(argProperty);
  }
 
  /**
   * @see java.lang.Object#clone()
   */
  @Override
  public ICloneable clone(){
    HashModel model = new HashModel();
    model.cloneFrom(this);
    return model;
  }
 
  // clears properties of the model, making sure the remove listeners
  // from any properties that are IModels
  private void cleanClear(){
    Iterator<ModelProperty> it = propertyMap.values().iterator();
    while(it.hasNext()){
      removeListener(it.next().prop);
      it.remove();
    }
  }
 
  /**
   * Clones from another HashModel, and makes sure to copy any values
   * in the model that are {@link ICloneable}. It watches for references
   *  to <code>argOther</code> and sets them to <code>this</code>.
   * @see com.dmurph.mvc.ICloneable#cloneFrom(com.dmurph.mvc.ICloneable)
   */
  public synchronized void cloneFrom(ICloneable argOther) {
    if(argOther instanceof HashModel){
      cleanClear();
      HashModel other = (HashModel) argOther;
           
      for(String key: other.propertyMap.keySet()){
        ModelProperty mp = other.propertyMap.get(key);
        registerProperty(key, mp.type);
       
        // references itself
        if(mp.prop == argOther){
          setProperty(key, this);
          continue;
        }
       
        setProperty(key, cloneImpl(key, mp.prop));
      }
    }else{
      throw new RuntimeException("Not a HashModel");
    }
  }
 
  /**
   * If false, this is the equivalent of {@link #saveChanges()}
   * @see IDirtyable#setDirty(boolean)
   */
  public synchronized void setDirty(boolean argDirty) {
    setProperty(DIRTY, argDirty);
    if(argDirty == false){
      for(String key: propertyMap.keySet()){
        ModelProperty mp = propertyMap.get(key);
        if(mp.prop instanceof IDirtyable){
          setDirtyImpl(key, (IDirtyable) mp.prop);
        }
      }
      saveChanges();
    }
  }
 
  /**
   * @see com.dmurph.mvc.IDirtyable#isDirty()
   */
  public synchronized boolean isDirty() {
    if((Boolean)getProperty(DIRTY)){
      return true;
    }
    for(PropertyWrapper prop : revertibleSupport.getRecordedProperties()){
      if(prop.isDirty()){
        return true;
      }
    }
    boolean ret = false;
    for(String key: propertyMap.keySet()){
      ModelProperty mp = propertyMap.get(key);
      if(mp.prop instanceof IDirtyable){
        ret = ret || isDirtyImpl(key, (IDirtyable) mp.prop);
        if(ret){
          return ret;
        }
      }
    }
    return ret;
  }

  /**
   * @see com.dmurph.mvc.IRevertible#revertChanges()
   */
  public synchronized void revertChanges() {
    revertibleSupport.revertChanges();
    for(String key: propertyMap.keySet()){
      ModelProperty mp = propertyMap.get(key);
      if(mp.prop instanceof IRevertible){
        revertChangesImpl(key, (IRevertible) mp.prop);
      }
    }
    setProperty(DIRTY, false);
  }
 
  /**
   * @see com.dmurph.mvc.IRevertible#saveChanges()
   */
  public synchronized void saveChanges() {
    setProperty(DIRTY, false);
    revertibleSupport.saveChanges();
    for(String key: propertyMap.keySet()){
      ModelProperty mp = propertyMap.get(key);
      if(mp.prop instanceof IRevertible){
        saveChangesImpl(key, (IRevertible) mp.prop);
      }
    }
  }
 
  /**
   * @see java.lang.Object#toString()
   */
  public String toString(){
    StringBuilder sb = new StringBuilder();
    sb.append("HashModel[");
    for(String s: propertyMap.keySet()){
      sb.append(s);
      sb.append("=");
      sb.append(propertyMap.get(s).prop);
      sb.append(", ");
    }
    sb.delete(sb.length()-2, sb.length());
    sb.append("]");
    return sb.toString();
  }
 
  public void printModel(){
    System.out.println(toString());
  }

  /**
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((propertyMap == null) ? 0 : propertyMap.hashCode());
    return result;
  }

  /**
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    HashModel other = (HashModel) obj;
    if (propertyMap == null) {
      if (other.propertyMap != null)
        return false;
    }
    else if (!propertyMap.equals(other.propertyMap))
      return false;
    return true;
  }


  private static class ModelProperty{
    PropertyType type;
    Object prop;
    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((prop == null) ? 0 : prop.hashCode());
      result = prime * result + ((type == null) ? 0 : type.hashCode());
      return result;
    }
    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      ModelProperty other = (ModelProperty) obj;
      if (prop == null) {
        if (other.prop != null)
          return false;
      }
      else if (!prop.equals(other.prop))
        return false;
      if (type != other.type)
        return false;
      return true;
    }
  }
}
TOP

Related Classes of com.dmurph.mvc.model.HashModel$ModelProperty

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.