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

net.sf.cuf.state.AbstractStateAdapter Maven / Gradle / Ivy

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

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

/**
 * This abstract implementation of StateAdapter handels all of the
 * bookkeeping work and delegates the "real" work (adapting of a state
 * change to a target object) to its subclasses.
 */
public abstract class AbstractStateAdapter implements StateAdapter, ChangeListener
{
    /** our adaption targets, key= target object, value invert flag (TRUE or FALSE) */
    private Map    mTargets;
    /** our state, may be null */
    private State                   mState;
    /** our name, never null */
    private String                  mName;

    /**
     * contains weak references to ALL objects added as targets
     * to ANY derived instance of AbstractStateAdapter.
     * This map is used to check if any object 
     * is a target for more than one adapter of a certain type.
     * 

* The keys are the categories of the derived classes * as returned by {@link #getConsistencyCategory()}, * the values are Lists of WeakReferences to the targets * that have been registered for those classes. * * @see #checkDuplicateTarget(Object) */ private static final Map>> CONSISTENCY_MAP = new HashMap<>(); /** * Creates a new adapter, that monitors no state and has an * empty string as its name. */ public AbstractStateAdapter() { mTargets= new HashMap<>(); mState = null; mName = ""; } /** * Creates a new adapter, that monitors the handed state and has an * empty string as its name. * @param pState the state we adopt to, may be null */ public AbstractStateAdapter(final State pState) { this(); setState(pState); } /** * Creates a new adapter, that monitors the handed state and uses * the handed string as its name. * @param pState the state we adopt to, may be null * @param pName our name * @throws IllegalArgumentException if pName is null */ public AbstractStateAdapter(final State pState, final String pName) { this(); setState(pState); setName(pName); } public String getName() { return mName; } public void setName(final String pName) { if (pName==null) { throw new IllegalArgumentException("name must not be null"); } mName= pName; } public void setState(final State pState) { if (mState!=null) { mState.removeChangeListener(this); } mState= pState; if (mState!=null) { mState.addChangeListener(this); // adjust initial state boolean stateEnabled= mState.isEnabled(); for (final Map.Entry nextEntry : mTargets.entrySet()) { Object target = nextEntry.getKey(); boolean invert = nextEntry.getValue(); boolean enabled= invert ? !stateEnabled : stateEnabled; adjustInitialState(target, enabled); } } } public void add(final Object pTarget) { add(pTarget, false); } public void addInvert(final Object pTarget) { add(pTarget, true); } public void add(final Object pTarget, final boolean pInvert) { if (pTarget==null) { throw new IllegalArgumentException("target must not be null"); } checkDuplicateTarget( pTarget); // adjust initial state boolean enabled= (pInvert ? !mState.isEnabled() : mState.isEnabled()); adjustInitialState(pTarget, enabled); mTargets.put(pTarget, pInvert ? Boolean.TRUE : Boolean.FALSE); } /** * checks if the given target has already been used by an adapter * of the same class as this one by checking in the * {@link #CONSISTENCY_MAP}. * If that is the case, an exception is thrown. Otherwise * the target is added to the map. * The key that is used to access the map is the one * returned by {@link #getConsistencyCategory()}. *

* Note that this does not check through the widget hierarchy if a widget * may be affected by a "deep" operation. * * @param pTarget the target * @throws IllegalArgumentException if the target has already been registered for the same category */ private void checkDuplicateTarget(final Object pTarget) { String category = getConsistencyCategory(); List> targetList = CONSISTENCY_MAP.get(category); if (targetList==null) { targetList = new ArrayList<>(); CONSISTENCY_MAP.put(category, targetList); targetList.add(new WeakReference<>(pTarget)); return; // OK } // we have to check the whole list, since we can't assume reasonable hash values for the targets for (Iterator> iter = targetList.iterator(); iter.hasNext();) { WeakReference previousTarget = iter.next(); if (previousTarget.get()==null) { // the reference has been collected, remove it from our list iter.remove(); } else //noinspection ObjectEquality if (pTarget==previousTarget.get()) { // we have a match - reject throw new IllegalArgumentException( "target "+pTarget+" is already in use in state adapter category "+category); } } // not found targetList.add(new WeakReference<>(pTarget)); } /** * This implementation returns the full class name to be used * as a key in the {@link #CONSISTENCY_MAP}. * Override to match if your implementation affects the same * property of a target as another implementation. * * @return the category for which the duplicate check should be done, * used as key in the {@link #CONSISTENCY_MAP} */ protected String getConsistencyCategory() { return getClass().getName(); } public void remove(final Object pComponent) { mTargets.remove(pComponent); // remove from the consistency check String category = getConsistencyCategory(); List> targetList = CONSISTENCY_MAP.get(category); if (targetList==null) { // unknown component - ignore return; } // we have to check the whole list, since we can't assume reasonable hash values for the targets for (Iterator> iter = targetList.iterator(); iter.hasNext();) { WeakReference previousTarget = iter.next(); if (previousTarget.get()==null) { // the reference has been collected, remove it from our list iter.remove(); } else //noinspection ObjectEquality if (pComponent==previousTarget.get()) { // we have a match - remove iter.remove(); } } } /** * Internal method to monitor our state, don't call this. * @param pEvent not used */ public void stateChanged(final ChangeEvent pEvent) { boolean stateEnabled= mState.isEnabled(); for (final Map.Entry nextEntry : mTargets.entrySet()) { Object target = nextEntry.getKey(); boolean invert = nextEntry.getValue(); boolean enabled= invert ? !stateEnabled : stateEnabled; processStateChange(target, enabled); } } /** * Implement this method in a subclass to set the initial state * of a target object. * @param pTarget target object, never null * @param pEnabled true if the target object should get "enabled" */ protected abstract void adjustInitialState(Object pTarget, boolean pEnabled); /** * Implement this method in a subclass to set the state of a target * object after our monitored state changes. * @param pTarget target object, never null * @param pEnabled true if the target object should get "enabled" */ protected abstract void processStateChange(Object pTarget, boolean pEnabled); }