/******************************************************************************* * Copyright (c) 2001, 2008 Oracle Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Oracle Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jst.jsf.common.runtime.internal.model; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.jst.jsf.common.runtime.internal.model.decorator.Decorator; /** * The base type of all objects that participate in the lifecycle of a view. * This may be used to hold either design time or runtime information. * * @author cbateman * */ public abstract class ViewObject implements Serializable /* IAdaptable? */ { private final ViewObjectData _data; /** * */ private static final long serialVersionUID = 1592205691642453075L; /** * */ protected ViewObject() { this(new ViewObjectData(false)); } /** * @param delegate */ protected ViewObject(final ViewObjectData delegate) { _data = delegate; _data.setOwner(this); } /** * @return the object containing all this view object's data */ protected ViewObjectData getData() { return _data; } /** * IMPORTANT: if this method returns false, then calling a mutator method * (basically anything that is not a get/isX() will throw any * IllegalStateException). * * @return if the object can still be modified. */ public final boolean isModifiable() { return !getData().isProtected(); } /** * Calling this method sets the state to not modifiable */ public final void setProtected() { getData().setLocked(); } /** * This call may be create a new data structure and should be considered of * much higher cost than most calls. * * @return all decorators of this object. List should be assumed by clients * to be unmodifiable and may throw mutation exceptions */ public List getAllDecorators() { final int size = getDecoratorMap().size(); if (size == 0) { return Collections.EMPTY_LIST; } final List allDecorators = new ArrayList(); for (final Iterator entryIt = getDecoratorMap().entrySet().iterator(); entryIt .hasNext();) { final Map.Entry entry = (Map.Entry) entryIt.next(); final List decorators = (List) entry.getValue(); allDecorators.addAll(decorators); } return Collections.unmodifiableList(allDecorators); } /** * @param decoratorType * @return all decorators of this object associated with the class type. The * returned list should be assumed to be unmodifiable. Returns an * empty list if no decorators are associated with decoratorType */ public List getDecorators(final Class decoratorType) { final List decorators = (List) getDecoratorMap().get(decoratorType); if (decorators == null) { return Collections.EMPTY_LIST; } return Collections.unmodifiableList(decorators); } /** * Add the decorator using decorator.getClass to associate the type. Must be * equivalent to addDecorator(decorator, decorator.getClass()); * * @param decorator * the decorator to add. <b>Must NOT be null.</b> * @throws IllegalArgumentException * if decorator is null. */ public void addDecorator(final Decorator decorator) { _data.addDecorator(decorator); } /** * Add the decorator using the associatedType. Normally you should call * {@link #addDecorator(Decorator)} however in some cases you may wish to * associated a decorator with a class type other than its own. For example, * sub-classes of ValidatorDecorator will want to associated with * ValidatorDecorator.getClass() in most cases so that it is included in any * common examination of component validators. * * @param decorator * to add. <b>Must NOT be null.</b> * @param associatedType * the key to store decorator <b>Must NOT be null</b> * @throws IllegalArgumentException * if decorator or associatedType is null */ public void addDecorator(final Decorator decorator, final Class associatedType) { _data.addDecorator(decorator, associatedType); } /** * Removes decorator if it is associated to associatedType on this view * object. * * @param decorator * @param associatedType * @return true if the decorator was found and removed. * @throws IllegalArgumentException * if decorator or associatedType is null */ public boolean removeDecorator(final Decorator decorator, final Class associatedType) { return _data.removeDecorator(decorator, associatedType); } /** * <p> * Get the adapter associated the class key. * </p> * * <p> * If addAdapter has been called with adapterType, then this adapter object * should always be called. In the default implementation, if there is no * explicit adapter, "this" is returned if (this instanceof adapterType). * * @param adapterType * @return the interface adapter associated with the class key or null if * not found */ public Object getAdapter(final Class adapterType) { if (adapterType == null) { return null; } Object adapter = getAdapterMap().get(adapterType); if (adapter == null) { if (adapterType.isInstance(this)) { adapter = this; } } return adapter; } /** * Adds the interface adapter object under adapterType key. There can be at * most one adapter registered for each class key. * * It is an error (throws exception) to try to add an adapter for * adapterType which this is already instance. This restriction is necessary * because otherwise local getters/setters would need to be aware of the * adapter mechanism and verify inheritance hierarchies on every calls. This * mechanism is intended only for adding interfaces to view object impls * that don't already have them. * * @param adapterType * @param adapter * @throws IllegalArgumentException * if adapterType or adapter is null or if casting adapter to * adapterType would * cause a ClassCastException (i.e. if * !(adapter instanceof adapterType)) OR if this is already an * instance of adapterType. */ public void addAdapter(final Class adapterType, final Object adapter) { _data.addAdapter(adapterType, adapter); } /** * Note that {@link #getAdapter(Class)} may still return non-null after this * is called if (this instanceof adapterType). * * @param adapterType * @return the adapter for adapterType that was just removed or null if not * found */ public Object removeAdapter(final Class adapterType) { return getAdapterMap().remove(adapterType); } /** * Note that this only returns those adapters added using * {@link #addAdapter(Class, Object)}. It does not return any implicit * adapters resulting from (this instanceof type). * * @return the map of all adapters. Maps is immutable and may throw * exceptions on attempts to mutate. */ public Map getAllAdapters() { if (getAdapterMap().size() == 0) { return Collections.EMPTY_MAP; } return Collections.unmodifiableMap(getAdapterMap()); } /** * <p> * The contract for this method is that it must always return a usable Map * and that map must be the same on every call. Lazy construction may be * used (as it is by default). The default map size is 4 and load factor is * 3 meaning that there should be decent tradeoff between wasted table size * and overhead used to increase it should the number of decorators exceed * 3. <b>Must never return null.</b> * </p> * * <p> * Generally, the method should not need to be overridden, however it is * provided to allow sub-classes to change the way the decorators map is * constructed. * </p> * * @return the map containing lists of decorators keyed by class. * */ protected Map getDecoratorMap() { return _data.getDecoratorMap(); } /** * <p> * The contract for this method is that it must always return a usable Map * and that map must be the same on every call. Lazy construction may be * used (as it is by default). The default map size is 4 and load factor is * 3 meaning that there should be decent tradeoff between wasted table size * and overhead used to increase it should the number of decorators exceed * 3. <b>Must never return null.</b> * </p> * * <p> * Generally, the method should not need to be overridden, however it is * provided to allow sub-classes to change the way the decorators map is * constructed. * </p> * * @return the map containing lists of adapters keyed by class. * */ protected Map getAdapterMap() { return _data.getAdapterMap(); } // ALL ViewObject's must use reference equals public final boolean equals(final Object obj) { return super.equals(obj); } public final int hashCode() { return super.hashCode(); } /** * The protectable view object data. * */ public static class ViewObjectData extends ProtectedDataObject { /** * */ private static final long serialVersionUID = -4216980607447926035L; private Map _decorators; private Map _adapters; private Object _owner; /** * @param isProtected */ public ViewObjectData(final boolean isProtected) { super(isProtected); } private void setOwner(final ViewObject viewObject) { _owner = viewObject; } /** * For serialization only. */ // public ViewObjectData() // { // // for serializability // super(false); // } /** * @param decorator * @param associatedType * @return true if the decorator was removed. */ public boolean removeDecorator(final Decorator decorator, final Class associatedType) { enforceProtection(); if (decorator == null || associatedType == null) { throw new IllegalArgumentException("Arguments must not be null"); //$NON-NLS-1$ } final List decoratorsByType = (List) getDecoratorMap().get( associatedType); if (decoratorsByType != null) { return decoratorsByType.remove(decorator); } return false; } /** * @param adapterType * @param adapter */ public void addAdapter(final Class adapterType, final Object adapter) { enforceProtection(); if (adapterType == null || adapter == null) { throw new IllegalArgumentException("Arguments must not be null"); //$NON-NLS-1$ } if (!adapterType.isInstance(adapter)) { throw new IllegalArgumentException("adapter: " + adapter //$NON-NLS-1$ + " must be cast compatible to class: " + adapterType); //$NON-NLS-1$ } else if (adapterType.isInstance(_owner)) { throw new IllegalArgumentException("this: " + _owner //$NON-NLS-1$ + " must not already be an instance of class: " //$NON-NLS-1$ + adapterType); } getAdapterMap().put(adapterType, adapter); } /** * @param decorator */ public void addDecorator(final Decorator decorator) { enforceProtection(); if (decorator == null) { throw new IllegalArgumentException("Arguments must not be null"); //$NON-NLS-1$ } final Class associationType = decorator.getClass(); addDecorator(decorator, associationType); } /** * @param decorator * @param associatedType */ public void addDecorator(final Decorator decorator, final Class associatedType) { enforceProtection(); if (decorator == null || associatedType == null) { throw new IllegalArgumentException("Arguments must not be null"); //$NON-NLS-1$ } List decoratorsByType = (List) getDecoratorMap().get(associatedType); if (decoratorsByType == null) { decoratorsByType = new ArrayList(2); getDecoratorMap().put(associatedType, decoratorsByType); } decoratorsByType.add(decorator); } /** * @return the decorator map, creating it if necessary */ protected synchronized Map getDecoratorMap() { if (_decorators == null) { if (isProtected()) { _decorators = Collections.EMPTY_MAP; } _decorators = new HashMap(4); } return _decorators; } /** * @return the adapter map, creating if necessary. */ protected synchronized Map getAdapterMap() { if (_adapters == null) { _adapters = new HashMap(4); } return _adapters; } } /** * An object that enforces that mutation can only happen up to point where * the object is designated protected at which point is it is forever * immutable. * */ public static abstract class ProtectedDataObject implements Serializable { /** * */ private static final long serialVersionUID = 4470279408370430399L; private boolean _isProtected; /** * @param isProtected */ public ProtectedDataObject(final boolean isProtected) { _isProtected = isProtected; } /** * @throws UnsupportedOperationException */ protected final synchronized void enforceProtection() throws UnsupportedOperationException { if (isProtected()) { throw new UnsupportedOperationException("Object "+this.toString()+ " is locked for modification"); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * @return true if this object is protected and irrevocablly immutable. */ public final synchronized boolean isProtected() { return _isProtected; } /** * Executed right before setProtected irrevocably sets the protection * flag. Does nothing by default */ protected void doBeforeProtecting() { // do nothing by default } /** * Makes this object irrevocably immutable. */ public final synchronized void setLocked() { doBeforeProtecting(); _isProtected = true; } } }