All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dmurph.mvc.model.HashModel Maven / Gradle / Ivy

/**
 * Copyright (c) 2010 Daniel Murphy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/**
 * 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dmurph.mvc.I18n;
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.
*
* 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 AbstractMVCSupport#cloneImpl(String, Object)}, {@link AbstractMVCSupport#revertChangesImpl(String, Object)}, * {@link AbstractMVCSupport#isDirtyImpl(String, Object)}, {@link AbstractMVCSupport#setDirtyImpl(String, Object)}, * or {@link AbstractMVCSupport#saveChangesImpl(String, Object)} 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 static final Logger log = LoggerFactory.getLogger(HashModel.class); private final HashMap propertyMap = new HashMap(); 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, with initial value of null. * @param argKey * @param argType the change permissions of the property. */ protected void registerProperty(String argKey, PropertyType argType){ registerProperty(argKey, argType, null); } /** * Register a property with an initial value. This is the only way to set * {@link PropertyType#READ_ONLY} properties. * @param argKey * @param argType the change permissions of the property * @param argInitial * @throws PropertyPermissionException thrown if someone tries to set the property of a {@link PropertyType#FINAL} * property. */ 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){ throw new PropertyPermissionException(I18n.getText("model.propertyFinal", argKey)); } }else{ mp = new ModelProperty(); } mp.type = argType; mp.prop = argInitial; mp.name = argKey; 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) * @throws PropertyPermissionException thrown if someone tries to set the property of a {@link PropertyType#FINAL} * property. */ public synchronized Object setProperty(String argKey, Object argProperty){ if(propertyMap.containsKey(argKey)){ ModelProperty mp = propertyMap.get(argKey); switch(mp.type){ case 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; case READ_ONLY: if(log.isWarnEnabled()){ log.warn("Attempt to change the property of the READ_ONLY property '{}' from '{}' to '{}'", new Object[]{argKey, mp.prop, argProperty}); } return mp.prop; case FINAL: throw new PropertyPermissionException(I18n.getText("model.propertyFinal", argKey)); } return null; }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 ICloneable#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 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 argOther and sets them to this. * @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); setDirtyImpl(key, 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); ret = ret || isDirtyImpl(key, 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); revertChangesImpl(key, 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); saveChangesImpl(key, 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#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; String name; /** * @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; } /** * @see java.lang.Object#toString() */ @Override public String toString() { return name+"-"+prop+"-"+type; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy