blob: 9c36f726c4f57dd314b2ec0ed7d73d6ee971cf44 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}