org.apache.myfaces.trinidad.component.StampState Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of trinidad-api Show documentation
Show all versions of trinidad-api Show documentation
Public API for the Apache MyFaces Trinidad project
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.trinidad.component;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIPanel;
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
/**
* This class saves the state of stamp components.
*/
final class StampState implements Externalizable
{
public StampState()
{
_rows = Collections.emptyMap();
}
/**
* Clears all state except for the state associated with the
* give currencyObj
* @param skipCurrencyObj
*/
public void clear(Object skipCurrencyObj)
{
if (!_rows.isEmpty())
{
Iterator iter = _rows.keySet().iterator();
while(iter.hasNext())
{
DualKey dk = iter.next();
if (_eq(dk._key1, skipCurrencyObj))
continue;
iter.remove();
}
}
}
public void put(Object currencyObj, String key, Object value)
{
Map comparant = Collections.emptyMap();
if (_rows == comparant)
{
if (value == null)
return;
// =-=AEW Better default sizes
_rows = new HashMap(109);
}
DualKey dk = new DualKey(currencyObj, key);
// Make sure that if we're applying a null value, that we
// don't hold on to the key and retain the entry - just nuke
// the entry
if (value == null)
_rows.remove(dk);
else
_rows.put(dk, value);
}
public int size()
{
return _rows.size();
}
public Object get(Object currencyObj, String key)
{
DualKey dk = new DualKey(currencyObj, key);
return _rows.get(dk);
}
/**
* Save the per-row state of a given stamp, the state will be for the component
* itself, not including it's child/facet state.
*/
public static Object saveStampState(FacesContext context, UIComponent stamp)
{
RowState state = _createState(stamp);
return state;
}
/**
* Restore the per-row state of a given stamp.
*/
public static void restoreStampState(FacesContext context, UIComponent stamp,
Object stampState)
{
if (stampState != null)
{
RowState state = (RowState) stampState;
state.restoreRowState(stamp);
}
}
/**
* save the stamp state of just the children of the given component
* in the given table.
*/
@SuppressWarnings("unchecked")
public static Object saveChildStampState(
FacesContext context,
UIComponent stamp,
UIXCollection table)
{
int childCount = stamp.getChildCount();
// If we have any children, iterate through the map,
// saving state
if (childCount == 0)
return null;
Map childStateMap = null;
List children = stamp.getChildren();
for(int i=0; i < childCount; i++)
{
UIComponent child = children.get(i);
Object childState = table.saveStampState(context, child);
// Until we have one non-null entry, don't allocate the map.
// So: allocate the map if we encounter our first
// non-null childState
if (childState == null)
continue;
if (childStateMap == null)
{
childStateMap = new HashMap(childCount);
}
// Store a value into the array
childStateMap.put(child.getId(), childState);
}
return childStateMap;
}
/**
* Restore the stamp state of just the children of the given component
* in the given table.
*/
@SuppressWarnings("unchecked")
public static void restoreChildStampState(
FacesContext context,
UIComponent stamp,
UIXCollection table,
Object stampState)
{
if (stampState == null || !(stampState instanceof Map))
return;
List kids = stamp.getChildren();
Map state = (Map)stampState;
for (UIComponent kid : kids)
{
Object childState = state.get(kid.getId());
if (childState == null)
continue;
table.restoreStampState(context, kid, childState);
}
}
/**
* @todo can do better...
*/
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeInt(_rows.size());
if (_rows.isEmpty())
return;
HashMap map = new HashMap(_rows.size());
map.putAll(_rows);
if (_LOG.isFinest())
{
for(Map.Entry entry : map.entrySet())
{
_LOG.finest("Saving " + entry.getKey() + ", " + entry.getValue());
}
}
out.writeObject(map);
}
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException
{
int size = in.readInt();
if (size > 0)
_rows = (Map) in.readObject();
if (_LOG.isFinest())
{
for(Map.Entry entry : _rows.entrySet())
{
_LOG.finest("Restoring " + entry.getKey() + ", " + entry.getValue());
}
}
}
private static RowState _createState(UIComponent child)
{
RowState state;
if (child instanceof EditableValueHolder)
{
state = new EVHState();
state.saveRowState(child);
}
else if (child instanceof UIXCollection)
{
state = new TableState();
state.saveRowState(child);
}
else if (child instanceof UIXShowDetail)
{
state = SDState.getState((UIXShowDetail) child);
}
else
{
state = null;
}
return state;
}
private static boolean _eq(Object k1, Object k2)
{
if (k1 == null)
return k2 == null;
return k1.equals(k2);
}
// State for a single row
static private abstract class RowState implements Serializable
{
public RowState()
{
}
abstract public void saveRowState(UIComponent child);
abstract public void restoreRowState(UIComponent child);
abstract public boolean isNull();
private static final long serialVersionUID = 1L;
}
static private final class SDState extends RowState
{
/**
* Return cached, shared instances of SDState.
*/
static public RowState getState(UIXShowDetail child)
{
FacesBean bean = child.getFacesBean();
Boolean disclosed = (Boolean)bean.getLocalProperty(UIXShowDetail.DISCLOSED_KEY);
if (disclosed == null)
return _NULL;
else if (disclosed)
return _TRUE;
else
return _FALSE;
}
public SDState()
{
}
private SDState(Boolean disclosed)
{
_disclosed = disclosed;
}
@Override
public void saveRowState(UIComponent child)
{
FacesBean bean = ((UIXShowDetail)child).getFacesBean();
_disclosed = (Boolean)bean.getLocalProperty(UIXShowDetail.DISCLOSED_KEY);
}
@Override
public void restoreRowState(UIComponent child)
{
FacesBean bean = ((UIXShowDetail)child).getFacesBean();
bean.setProperty(UIXShowDetail.DISCLOSED_KEY, _disclosed);
}
@Override
public boolean isNull()
{
return _disclosed == null;
}
@Override
public String toString()
{
return "SDState[disclosed=" + _disclosed + "]";
}
// Reusable instances of SDState. TODO: use readResolve/writeReplace
// so that we only send across and restore instances of these
static private final SDState _TRUE = new SDState(true);
static private final SDState _FALSE = new SDState(false);
static private final SDState _NULL = new SDState(null);
/**
*
*/
private static final long serialVersionUID = -8605916495935317932L;
private Boolean _disclosed;
}
static private final class TableState extends RowState
{
public TableState()
{
}
@Override
public void saveRowState(UIComponent child)
{
_state = ((UIXCollection) child).__getMyStampState();
}
@Override
public void restoreRowState(UIComponent child)
{
//There is a bug in the RI where, on a PPR request, an UIPanel will sometimes be
//added to a facet that contains only one child in facelets. This, of course,
//changes the structure of the saved data. If we get a UIPanel here, then this
//but is the cause. It means we have a Collection which contains a switcher
//which in turn contains another Collection, and UIPanel was returned instead of
//the origional collection. Therefore, this UIPanel should ALWAYS only have one
//Item. If the facet contained more then one item, we would ALWAYS have a UIPanel
//and therefore we would be using a different stamp state.
UIXCollection myChild = (child instanceof UIPanel)?(UIXCollection)child.getChildren().get(0):(UIXCollection)child;
myChild.__setMyStampState(_state);
}
@Override
public boolean isNull()
{
return _state == null;
}
private Object _state = null;
private static final long serialVersionUID = 1L;
}
static private class EVHState extends RowState
{
public EVHState()
{
_valid = true;
}
@Override
public void saveRowState(UIComponent child)
{
assert _assertIsStampCorrect(child);
EditableValueHolder evh = (EditableValueHolder) child;
_submitted = evh.getSubmittedValue();
_localSet = evh.isLocalValueSet();
_local = evh.getLocalValue();
_valid = evh.isValid();
}
@Override
public void restoreRowState(UIComponent child)
{
assert _assertIsStampCorrect(child);
EditableValueHolder evh = (EditableValueHolder) child;
evh.setSubmittedValue(_submitted);
evh.setValue(_local);
evh.setLocalValueSet(_localSet);
evh.setValid(_valid);
assert _assertStampHonoursState(evh);
}
@Override
public boolean isNull()
{
return (_valid && (!_localSet) && (_submitted == null));
}
@Override
public String toString()
{
return "EVHState[value=" + _local + ",submitted=" + _submitted + "]";
}
private boolean _assertStampHonoursState(EditableValueHolder evh)
{
return (evh.getSubmittedValue() == _submitted) &&
(evh.getLocalValue() == _local) &&
(evh.isLocalValueSet() == _localSet) &&
(evh.isValid() == _valid);
}
/**
* Make sure that this stampState is used to save/restore state from the
* same stamp each time.
*/
private boolean _assertIsStampCorrect(UIComponent stamp)
{
if (_assertStamp != null)
{
String stampId = stamp.getId();
String assertStampId = _assertStamp.getId();
assert (((assertStampId == null) && (stampId == null)) ||
((assertStampId != null) && assertStampId.equals(stampId))) :
"Current stamp:"+stamp+
" with id:"+stamp.getId()+
". Previously stamp was:"+_assertStamp+
" with id:"+_assertStamp.getId();
}
else
{
_assertStamp = stamp;
}
return true;
}
private Object _submitted;
private Object _local;
private boolean _localSet;
private boolean _valid;
private transient UIComponent _assertStamp = null;
private static final long serialVersionUID = 1L;
}
private static final class DualKey implements Serializable
{
public DualKey(Object key1, Object key2)
{
_key1 = key1;
_key2 = key2;
_hash = _hash(key1) + _hash(key2);
}
@Override
public boolean equals(Object other)
{
if (other == this)
return true;
if (other instanceof DualKey)
{
DualKey otherKey = (DualKey) other;
if (hashCode() != otherKey.hashCode())
return false;
return _eq(_key1, otherKey._key1) && _eq(_key2, otherKey._key2);
}
return false;
}
@Override
public int hashCode()
{
return _hash;
}
@Override
public String toString()
{
return "<"+_key1+","+_key2+">";
}
private static int _hash(Object k)
{
return (k==null) ? 0 : k.hashCode();
}
private final Object _key1, _key2;
private final int _hash;
private static final long serialVersionUID = 1L;
}
private static final TrinidadLogger _LOG =
TrinidadLogger.createTrinidadLogger(StampState.class);
private Map _rows;
private static final Object[] _EMPTY_ARRAY = new Object[0];
private static final long serialVersionUID = 1L;
}