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

net.sf.cuf.model.AspectAdapter Maven / Gradle / Ivy

The newest version!
package net.sf.cuf.model;

import java.util.List;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * A AspectAdapter is a ValueModel that takes a part (=an aspect) of an
 * other object (called the source) as its value.
* To detect changes of the aspect, the source must be the value of * a ValueModel. If the source is not wrapped in a ValueModel, a * change in the source can't be detected.
* The aspect is described via a string like "attribut1/attribut2" * and getting/setting the aspect is done via reflection like * source.getAttribut1().getAttribut2() or * source.getAttribut1().setAttribut2().
* An alternative to the reflection access is via usage of the Map * get/put interface.
* The normal mode of operation is that a ValueModel holds some * "transport" level type of object, which usally is quite dump, * i.e. just a structure ("Value Object") with JavaBeans semantics.
* When different AspectAdapters wrap different parts of such * a structure, it must be carefully avoided that more than one * AspectAdapter maps (parts of) the same data, because those * two adapters won't notice each other changes.
* @param the type we contain as our value * @param the type the source is from */ public class AspectAdapter extends AbstractValueModel implements ValueModel, ChangeListener, DelegateAccess, ExternalUpdate { /** The separator between attributes. */ public static final String SEPARATOR= "."; /** null or the trigger */ private ValueModel mTrigger; /** null or mTrigger if the trigger supports an external update propagation */ private ExternalUpdate mTriggerUpdate; /** null or our source */ private Object mSource; /** the aspect the adapter was initialized with, never null */ private String mAspectName; /** our (base) source class (or interface), may be null */ private Class mSourceClass; /** our strategy to get/set the values */ private AspectAccessAdapter mAccessAdapter; /** Default get method name prefix. */ public static final String GET= "get"; /** Default set method name prefix. */ public static final String SET= "set"; /** * Create a new AspectAdapter or re-use an existing one. * This factory method uses the getDependents() method from the AbstractValueModel * to find already existing AspectAdapter's for the same aspect. * Warning: we don't search recursivly, so the aspect name must be exactly equal. * @param pTrigger the trigger, must not be null * @param pAspectName string describing the attributes (seperated by SEPARATOR) to our aspect * @return a new or shared aspect adapter */ public static AspectAdapter getShared(final ValueModel pTrigger, final String pAspectName) { if (pTrigger==null) throw new IllegalArgumentException("trigger must not be null"); AspectAdapter back= null; // search existing one if (pTrigger instanceof AbstractValueModel) { AbstractValueModel trigger= (AbstractValueModel)pTrigger; List dependents= trigger.getDependents(); for (final Object dependent : dependents) { if (dependent instanceof AspectAdapter) { AspectAdapter aspectAdapter= (AspectAdapter)dependent; if (aspectAdapter.getAspectName().equals(pAspectName)) { back= aspectAdapter; break; } } } } // create new AspectAdapter if we found no matching one if (back==null) { back= new AspectAdapter(pTrigger, pAspectName); } return back; } /** * Creates a new AspectAdapter. * @param pTrigger the source object of our aspect is the trigger's value * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @throws IllegalArgumentException if pTrigger is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final ValueModel pTrigger, final String pAspectName) { this(pTrigger, pAspectName, GET, SET); } /** * Creates a new AspectAdapter. * @param pTrigger the source object of our aspect is the trigger's value * @param pAspectName a string describing the attributes (separated by SEPARATOR) * to our aspect. * @param pGetterPrefix the prefix we use for the "getter", must not be null nor empty * @param pSetterPrefix the prefix we use for the "getter", must not be null nor empty nor the getter prefix * @throws IllegalArgumentException if pTrigger is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final ValueModel pTrigger, final String pAspectName, final String pGetterPrefix, final String pSetterPrefix) { super(); if (pTrigger==null) throw new IllegalArgumentException("trigger must not be null"); S source= pTrigger.getValue(); init(source, (source==null)? null : (Class)source.getClass(), null, pTrigger, pAspectName, pGetterPrefix, pSetterPrefix); } /** * Creates a new AspectAdapter. * @param pTrigger the source object of our aspect is the trigger's value * @param pSourceClass the class of the value of pTrigger, if this is a Map, the * Map.get() and Map.put() methods are used to access the aspect. * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @throws IllegalArgumentException if pTrigger is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final ValueModel pTrigger, final Class pSourceClass, final String pAspectName) { this(pTrigger, pSourceClass, null, pAspectName, GET, SET); } /** * Creates a new AspectAdapter. * @param pTrigger the source object of our aspect is the trigger's value * @param pSourceClass the class of the value of pTrigger, if this is a Map, the * Map.get() and Map.put() methods are used to access the aspect. * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @param pGetterPrefix the prefix we use for the "getter", must not be null nor empty * @param pSetterPrefix the prefix we use for the "getter", must not be null nor empty nor the getter prefix * @throws IllegalArgumentException if pTrigger is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final ValueModel pTrigger, final Class pSourceClass, final String pAspectName, final String pGetterPrefix, final String pSetterPrefix) { this(pTrigger, pSourceClass, null, pAspectName, pGetterPrefix, pSetterPrefix); } /** * Creates a new AspectAdapter. * @param pTrigger the source object of our aspect is the trigger's value * @param pSourceClass the class of the value of pTrigger, if this is a Map, the * Map.get() and Map.put() methods are used to access the aspect. * @param pAccessAdapter the access adapter, may be null * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @throws IllegalArgumentException if pTrigger is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final ValueModel pTrigger, final Class pSourceClass, final AspectAccessAdapter pAccessAdapter, final String pAspectName) { this(pTrigger, pSourceClass, pAccessAdapter, pAspectName, GET, SET); } /** * Creates a new AspectAdapter. * @param pTrigger the source object of our aspect is the trigger's value * @param pSourceClass the class of the value of pTrigger, if this is a Map, the * Map.get() and Map.put() methods are used to access the aspect. * @param pAccessAdapter the access adapter, may be null * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @param pGetterPrefix the prefix we use for the "getter", must not be null nor empty * @param pSetterPrefix the prefix we use for the "getter", must not be null nor empty nor the getter prefix * @throws IllegalArgumentException if pTrigger is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final ValueModel pTrigger, final Class pSourceClass, final AspectAccessAdapter pAccessAdapter, final String pAspectName, final String pGetterPrefix, final String pSetterPrefix) { super(); if (pTrigger==null) throw new IllegalArgumentException("trigger must not be null"); init(pTrigger.getValue(), pSourceClass, pAccessAdapter, pTrigger, pAspectName, pGetterPrefix, pSetterPrefix); } /** * Creates a new AspectAdapter. * @param pSource the source object of our aspect * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @throws IllegalArgumentException if pSource or pAspectName is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final S pSource, final String pAspectName) { super(); if (pSource==null) throw new IllegalArgumentException("source must not be null"); init(pSource, (Class)pSource.getClass(), null, null, pAspectName, GET, SET); } /** * Creates a new AspectAdapter. * @param pSource the source object of our aspect * @param pAspectName a string describing the attributes (seperated by SEPARATOR) * to our aspect. * @param pGetterPrefix the prefix we use for the "getter", must not be null nor empty * @param pSetterPrefix the prefix we use for the "getter", must not be null nor empty nor the getter prefix * @throws IllegalArgumentException if pSource or pAspectName is null * or pAspectName doesn't map to valid methods */ public AspectAdapter(final S pSource, final String pAspectName, final String pGetterPrefix, final String pSetterPrefix) { super(); if (pSource==null) throw new IllegalArgumentException("source must not be null"); init(pSource, (Class)pSource.getClass(), null, null, pAspectName, pGetterPrefix, pSetterPrefix); } /** * Handle common constructor stuff. * @param pSource source of our aspect, may be null * @param pSourceClass class of the source of our aspect, may be null * @param pAccessAdapter the access adapter, may be null * @param pTrigger trigger for our aspect, may be null * @param pAspectName attributes to our aspect * @param pGetterPrefix the prefix we use for the "getter", must not be null nor empty * @param pSetterPrefix the prefix we use for the "getter", must not be null nor empty nor the getter prefix */ private void init(final Object pSource, final Class pSourceClass, final AspectAccessAdapter pAccessAdapter, final ValueModel pTrigger, final String pAspectName, final String pGetterPrefix, final String pSetterPrefix) { if ((pAspectName==null) || (pAspectName.length()==0)) throw new IllegalArgumentException("aspect string must not be null or empty"); mSource = pSource; mSourceClass = pSourceClass; mTrigger = pTrigger; // initialize the value setInSetValue(false, false); if (mTrigger instanceof ExternalUpdate) { mTriggerUpdate= (ExternalUpdate) mTrigger; } else { mTriggerUpdate= null; } mAspectName = pAspectName; if (pAccessAdapter!=null) { mAccessAdapter= pAccessAdapter; } else { mAccessAdapter= new MixedAccessAdapter( pSourceClass, pAspectName, pGetterPrefix, pSetterPrefix); } if (mTrigger!=null) { mTrigger.addChangeListener(this); } } /** * @return the access adapter used to set and get values, never null */ public AspectAccessAdapter getAccessAdapter() { return mAccessAdapter; } /** * Sets the access adapter to use when setting or getting values. * @param pAccessAdapter the adapter to use, must not be null */ public void setAccessAdapter(final AspectAccessAdapter pAccessAdapter) { if (pAccessAdapter==null) throw new IllegalArgumentException( "pAccessAdapter must not be null"); mAccessAdapter = pAccessAdapter; } /** * Returns true if a setValue() is possible, and doesn't throw always an * UnsupportedOperationException. * @return false if setValue will always throw an exception */ public boolean isEditable() { return mAccessAdapter.isEditable(); } /** * Return the aspect of the aspect adapter. * @return the access string, never null */ public String getAspectName() { return mAspectName; } /** * Returns null or the trigger ValueModel of this aspect adapter. * @return null or a ValueModel */ public ValueModel getTrigger() { return mTrigger; } /** * Returns null or the source object of this aspect adapter. * @return the source, may be null */ public Object getSource() { return mSource; } /** * Cleanup all resources: disconnect from any input sources (like * other ValueModel's ...), and remove all listeners. */ public void dispose() { super.dispose(); if (mTrigger!=null && !mTrigger.isDisposed()) { mTrigger.removeChangeListener(this); } } /** * Set a new value, this will fire a ChangeEvent if the new value * is different from the old value. * If our source is null, the call does nothing. * @param pValue the new value (null is o.k.) * @param pIsSetForced true if a forced setValue should be done * @throws UnsupportedOperationException if this ValueModel is not mutable */ public void setValue(final T pValue, final boolean pIsSetForced) { checkDisposed(); if (!isEditable()) { throw new UnsupportedOperationException("value not mutable ("+mSourceClass+ ", aspect: "+mAspectName+')'); } if (mSource==null) { // silently ignore changes when there is no source return; } try { setInSetValue(true, pIsSetForced); mAccessAdapter.setValue(mSource, pValue); if (mTriggerUpdate!=null) { mTriggerUpdate.signalExternalUpdate(); } fireStateChanged(); } finally { setInSetValue(false, false); } } /** * Get the current value, during a callback this is the new value. * @return null or the value object */ public T getValue() { checkDisposed(); return (T)mAccessAdapter.getValue(mSource); } /** * Transform the handed value to a new value. * This should not change the current value or trigger any updates. * @param pValue the value we should assume as our value * @return null or the transformed/mapped value object */ public Object getValue(final Object pValue) { Object value= pValue; if (mTrigger instanceof DelegateAccess) { value= ((DelegateAccess)mTrigger).getValue(pValue); } return mAccessAdapter.getValue(value); } /** * Signal this object that portions of its data changed. * If we have a trigger, we notify it, and assume that it will notify us, * otherwise we notify our children directly. */ @Override public void signalExternalUpdate() { if (mTrigger instanceof ExternalUpdate) { ((ExternalUpdate)mTrigger).signalExternalUpdate(); } else { super.signalExternalUpdate(); } } /** * Signal this object that portions of its data changed. * If we have a trigger, we notify it, and assume that it will notify us, * otherwise we notify our children directly. * @param pChangeEvent the specific change information */ @Override public void signalExternalUpdate(ChangeEvent pChangeEvent) { if (mTrigger instanceof ExternalUpdate) { ((ExternalUpdate)mTrigger).signalExternalUpdate(pChangeEvent); } else { super.signalExternalUpdate(pChangeEvent); } } /** * Invoked when the target of the listener has changed its state. * * @param pEvent a ChangeEvent object */ public void stateChanged(final ChangeEvent pEvent) { checkDisposed(); // ignore callbacks when we are the reason that our model changes if (isInSetValue()) { return; } Object newValue = mTrigger.getValue(); boolean isSetForced= mTrigger.isSetForced(); if (!isSetForced) { //noinspection ObjectEquality if ((newValue==null && mSource==null) || (newValue==mSource)) { // nothing changed return; } } if ((newValue!=null) && (mSourceClass!=null) && (!mSourceClass.isAssignableFrom(newValue.getClass())) ) { throw new IllegalStateException("new value has wrong type (expected: "+ mSourceClass+", found: "+ newValue.getClass()+')'); } mSource= newValue; try { setInSetValue(false, isSetForced); fireStateChanged(); } finally { setInSetValue(false, false); } } }