/* | |
* 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.html5.behavior; | |
import java.io.Serializable; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import javax.el.ValueExpression; | |
import javax.faces.component.StateHelper; | |
import javax.faces.component.StateHolder; | |
import javax.faces.component.UIComponentBase; | |
import javax.faces.context.FacesContext; | |
/** | |
* @see javax.faces.component.behavior._AjaxBehaviorDeltaStateHelper of MyFaces Core | |
*/ | |
public class ClientBehaviorDeltaStateHelper <B extends ValueExpressionHolder> implements StateHelper { | |
/** | |
* We need to hold a component instance because: | |
* <p/> | |
* - The component is the one who knows if we are on initial or delta mode | |
* - eval assume calls to component.ValueExpression | |
*/ | |
private B _target; | |
/** | |
* This map holds the full current state | |
*/ | |
private Map<Serializable, Object> _fullState; | |
/** | |
* This map only keep track of delta changes to be saved | |
*/ | |
private Map<Serializable, Object> _deltas; | |
/** | |
* This map keep track of StateHolder keys, to be saved when | |
* saveState is called. | |
*/ | |
//private Set<Serializable> _stateHolderKeys; | |
private boolean _transient = false; | |
public ClientBehaviorDeltaStateHelper(B component) { | |
super(); | |
this._target = component; | |
_fullState = new HashMap<Serializable, Object>(); | |
_deltas = null; | |
//_stateHolderKeys = new HashSet<Serializable>(); | |
} | |
/** | |
* Used to create delta map on demand | |
* | |
* @return | |
*/ | |
private boolean _createDeltas() { | |
if (isInitialStateMarked()) { | |
if (_deltas == null) { | |
_deltas = new HashMap<Serializable, Object>(2); | |
} | |
return true; | |
} | |
return false; | |
} | |
protected boolean isInitialStateMarked() { | |
return _target.initialStateMarked(); | |
} | |
@SuppressWarnings("unchecked") | |
public void add(Serializable key, Object value) { | |
if (_createDeltas()) { | |
//Track delta case | |
Map<Object, Boolean> deltaListMapValues = (Map<Object, Boolean>) _deltas | |
.get(key); | |
if (deltaListMapValues == null) { | |
deltaListMapValues = new InternalDeltaListMap<Object, Boolean>( | |
3); | |
_deltas.put(key, deltaListMapValues); | |
} | |
deltaListMapValues.put(value, Boolean.TRUE); | |
} | |
//Handle change on full map | |
List<Object> fullListValues = (List<Object>) _fullState.get(key); | |
if (fullListValues == null) { | |
fullListValues = new InternalList<Object>(3); | |
_fullState.put(key, fullListValues); | |
} | |
fullListValues.add(value); | |
} | |
public Object eval(Serializable key) { | |
Object returnValue = _fullState.get(key); | |
if (returnValue != null) { | |
return returnValue; | |
} | |
ValueExpression expression = _target.getValueExpression(key | |
.toString()); | |
if (expression != null) { | |
return expression.getValue(FacesContext.getCurrentInstance() | |
.getELContext()); | |
} | |
return null; | |
} | |
public Object eval(Serializable key, Object defaultValue) { | |
Object returnValue = _fullState.get(key); | |
if (returnValue != null) { | |
return returnValue; | |
} | |
ValueExpression expression = _target.getValueExpression(key | |
.toString()); | |
if (expression != null) { | |
return expression.getValue(FacesContext.getCurrentInstance() | |
.getELContext()); | |
} | |
return defaultValue; | |
} | |
public Object get(Serializable key) { | |
return _fullState.get(key); | |
} | |
public Object put(Serializable key, Object value) { | |
Object returnValue = null; | |
if (_createDeltas()) { | |
if (_deltas.containsKey(key)) { | |
returnValue = _deltas.put(key, value); | |
_fullState.put(key, value); | |
} else { | |
_deltas.put(key, value); | |
returnValue = _fullState.put(key, value); | |
} | |
} else { | |
/* | |
if (value instanceof StateHolder) | |
{ | |
_stateHolderKeys.add(key); | |
} | |
*/ | |
returnValue = _fullState.put(key, value); | |
} | |
return returnValue; | |
} | |
@SuppressWarnings("unchecked") | |
public Object put(Serializable key, String mapKey, Object value) { | |
boolean returnSet = false; | |
Object returnValue = null; | |
if (_createDeltas()) { | |
//Track delta case | |
Map<String, Object> mapValues = (Map<String, Object>) _deltas | |
.get(key); | |
if (mapValues == null) { | |
mapValues = new InternalMap<String, Object>(); | |
_deltas.put(key, mapValues); | |
} | |
if (mapValues.containsKey(mapKey)) { | |
returnValue = mapValues.put(mapKey, value); | |
returnSet = true; | |
} else { | |
mapValues.put(mapKey, value); | |
} | |
} | |
//Handle change on full map | |
Map<String, Object> mapValues = (Map<String, Object>) _fullState | |
.get(key); | |
if (mapValues == null) { | |
mapValues = new InternalMap<String, Object>(); | |
_fullState.put(key, mapValues); | |
} | |
if (returnSet) { | |
mapValues.put(mapKey, value); | |
} else { | |
returnValue = mapValues.put(mapKey, value); | |
} | |
return returnValue; | |
} | |
public Object remove(Serializable key) { | |
Object returnValue = null; | |
if (_createDeltas()) { | |
if (_deltas.containsKey(key)) { | |
// Keep track of the removed values using key/null pair on the delta map | |
returnValue = _deltas.put(key, null); | |
_fullState.remove(key); | |
} else { | |
// Keep track of the removed values using key/null pair on the delta map | |
_deltas.put(key, null); | |
returnValue = _fullState.remove(key); | |
} | |
} else { | |
returnValue = _fullState.remove(key); | |
} | |
return returnValue; | |
} | |
@SuppressWarnings("unchecked") | |
public Object remove(Serializable key, Object valueOrKey) { | |
// Comment by lu4242 : The spec javadoc says if it is a Collection | |
// or Map deal with it. But the intention of this method is work | |
// with add(?,?) and put(?,?,?), this ones return instances of | |
// InternalMap and InternalList to prevent mixing, so to be | |
// consistent we'll cast to those classes here. | |
Object collectionOrMap = _fullState.get(key); | |
Object returnValue = null; | |
if (collectionOrMap instanceof InternalMap) { | |
if (_createDeltas()) { | |
returnValue = _removeValueOrKeyFromMap(_deltas, key, | |
valueOrKey, true); | |
_removeValueOrKeyFromMap(_fullState, key, valueOrKey, false); | |
} else { | |
returnValue = _removeValueOrKeyFromMap(_fullState, key, | |
valueOrKey, false); | |
} | |
} else if (collectionOrMap instanceof InternalList) { | |
if (_createDeltas()) { | |
returnValue = _removeValueOrKeyFromCollectionDelta(_deltas, | |
key, valueOrKey); | |
_removeValueOrKeyFromCollection(_fullState, key, valueOrKey); | |
} else { | |
returnValue = _removeValueOrKeyFromCollection(_fullState, key, | |
valueOrKey); | |
} | |
} | |
return returnValue; | |
} | |
@SuppressWarnings("unchecked") | |
private static Object _removeValueOrKeyFromCollectionDelta( | |
Map<Serializable, Object> stateMap, Serializable key, | |
Object valueOrKey) { | |
Object returnValue = null; | |
Map<Object, Boolean> c = (Map<Object, Boolean>) stateMap.get(key); | |
if (c != null) { | |
if (c.containsKey(valueOrKey)) { | |
returnValue = valueOrKey; | |
} | |
c.put(valueOrKey, Boolean.FALSE); | |
} | |
return returnValue; | |
} | |
@SuppressWarnings("unchecked") | |
private static Object _removeValueOrKeyFromCollection( | |
Map<Serializable, Object> stateMap, Serializable key, | |
Object valueOrKey) { | |
Object returnValue = null; | |
Collection c = (Collection) stateMap.get(key); | |
if (c != null) { | |
if (c.remove(valueOrKey)) { | |
returnValue = valueOrKey; | |
} | |
if (c.isEmpty()) { | |
stateMap.remove(key); | |
} | |
} | |
return returnValue; | |
} | |
@SuppressWarnings("unchecked") | |
private static Object _removeValueOrKeyFromMap( | |
Map<Serializable, Object> stateMap, Serializable key, | |
Object valueOrKey, boolean delta) { | |
if (valueOrKey == null) { | |
return null; | |
} | |
Object returnValue = null; | |
Map<String, Object> map = (Map<String, Object>) stateMap.get(key); | |
if (map != null) { | |
if (delta) { | |
// Keep track of the removed values using key/null pair on the delta map | |
returnValue = map.put((String) valueOrKey, null); | |
} else { | |
returnValue = map.remove(valueOrKey); | |
} | |
if (map.isEmpty()) { | |
//stateMap.remove(key); | |
stateMap.put(key, null); | |
} | |
} | |
return returnValue; | |
} | |
public boolean isTransient() { | |
return _transient; | |
} | |
/** | |
* Serializing cod | |
* the serialized data structure consists of key value pairs unless the value itself is an internal array | |
* or a map in case of an internal array or map the value itself is another array with its initial value | |
* myfaces.InternalArray, myfaces.internalMap | |
* <p/> | |
* the internal Array is then mapped to another array | |
* <p/> | |
* the internal Map again is then mapped to a map with key value pairs | |
*/ | |
@SuppressWarnings("unchecked") | |
public Object saveState(FacesContext context) { | |
Map serializableMap = (isInitialStateMarked()) ? _deltas : _fullState; | |
if (serializableMap == null || serializableMap.size() == 0) { | |
return null; | |
} | |
/* | |
int stateHolderKeyCount = 0; | |
if (isInitalStateMarked()) | |
{ | |
for (Iterator<Serializable> it = _stateHolderKeys.iterator(); it.hasNext();) | |
{ | |
Serializable key = it.next(); | |
if (!_deltas.containsKey(key)) | |
{ | |
stateHolderKeyCount++; | |
} | |
} | |
}*/ | |
Map.Entry<Serializable, Object> entry; | |
//entry == key, value, key, value | |
Object[] retArr = new Object[serializableMap.entrySet().size() * 2]; | |
//Object[] retArr = new Object[serializableMap.entrySet().size() * 2 + stateHolderKeyCount]; | |
Iterator<Map.Entry<Serializable, Object>> it = serializableMap | |
.entrySet().iterator(); | |
int cnt = 0; | |
while (it.hasNext()) { | |
entry = it.next(); | |
retArr[cnt] = entry.getKey(); | |
Object value = entry.getValue(); | |
// The condition in which the call to saveAttachedState | |
// is to handle List, StateHolder or non Serializable instances. | |
// we check it here, to prevent unnecessary calls. | |
if (value instanceof StateHolder || | |
value instanceof List || | |
!(value instanceof Serializable)) { | |
Object savedValue = UIComponentBase.saveAttachedState(context, | |
value); | |
retArr[cnt + 1] = savedValue; | |
} else { | |
retArr[cnt + 1] = value; | |
} | |
cnt += 2; | |
} | |
/* | |
if (isInitalStateMarked()) | |
{ | |
for (Iterator<Serializable> it2 = _stateHolderKeys.iterator(); it.hasNext();) | |
{ | |
Serializable key = it2.next(); | |
if (!_deltas.containsKey(key)) | |
{ | |
retArr[cnt] = key; | |
Object value = _fullState.get(key); | |
if (value instanceof PartialStateHolder) | |
{ | |
//Could contain delta, save it as _AttachedDeltaState | |
PartialStateHolder holder = (PartialStateHolder) value; | |
if (holder.isTransient()) | |
{ | |
retArr[cnt + 1] = null; | |
} | |
else | |
{ | |
retArr[cnt + 1] = new _AttachedDeltaWrapper(value.getClass(), holder.saveState(context)); | |
} | |
} | |
else | |
{ | |
//Save everything | |
retArr[cnt + 1] = UIComponentBase.saveAttachedState(context, _fullState.get(key)); | |
} | |
cnt += 2; | |
} | |
} | |
} | |
*/ | |
return retArr; | |
} | |
@SuppressWarnings("unchecked") | |
public void restoreState(FacesContext context, Object state) { | |
if (state == null) | |
return; | |
Object[] serializedState = (Object[]) state; | |
for (int cnt = 0; cnt < serializedState.length; cnt += 2) { | |
Serializable key = (Serializable) serializedState[cnt]; | |
Object savedValue = UIComponentBase.restoreAttachedState(context, | |
serializedState[cnt + 1]); | |
if (isInitialStateMarked()) { | |
if (savedValue instanceof InternalDeltaListMap) { | |
for (Map.Entry<Object, Boolean> mapEntry : ((Map<Object, Boolean>) savedValue) | |
.entrySet()) { | |
boolean addOrRemove = mapEntry.getValue(); | |
if (addOrRemove) { | |
//add | |
this.add(key, mapEntry.getKey()); | |
} else { | |
//remove | |
this.remove(key, mapEntry.getKey()); | |
} | |
} | |
} else if (savedValue instanceof InternalMap) { | |
for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) savedValue) | |
.entrySet()) { | |
this.put(key, mapEntry.getKey(), mapEntry.getValue()); | |
} | |
} | |
/* | |
else if (savedValue instanceof _AttachedDeltaWrapper) | |
{ | |
_AttachedStateWrapper wrapper = (_AttachedStateWrapper) savedValue; | |
//Restore delta state | |
((PartialStateHolder)_fullState.get(key)).restoreState(context, wrapper.getWrappedStateObject()); | |
//Add this key as StateHolder key | |
_stateHolderKeys.add(key); | |
} | |
*/ | |
else { | |
put(key, savedValue); | |
} | |
} else { | |
put(key, savedValue); | |
} | |
} | |
} | |
public void setTransient(boolean transientValue) { | |
_transient = transientValue; | |
} | |
//We use our own data structures just to make sure | |
//nothing gets mixed up internally | |
@SuppressWarnings("serial") | |
static class InternalMap<K, V> extends HashMap<K, V> implements StateHolder { | |
public InternalMap() { | |
super(); | |
} | |
public InternalMap(int initialCapacity, float loadFactor) { | |
super(initialCapacity, loadFactor); | |
} | |
public InternalMap(Map<? extends K, ? extends V> m) { | |
super(m); | |
} | |
public InternalMap(int initialSize) { | |
super(initialSize); | |
} | |
public boolean isTransient() { | |
return false; | |
} | |
public void setTransient(boolean newTransientValue) { | |
// No op | |
} | |
@SuppressWarnings("unchecked") | |
public void restoreState(FacesContext context, Object state) { | |
Object[] listAsMap = (Object[]) state; | |
for (int cnt = 0; cnt < listAsMap.length; cnt += 2) { | |
this.put((K) listAsMap[cnt], (V) UIComponentBase | |
.restoreAttachedState(context, listAsMap[cnt + 1])); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
public Object saveState(FacesContext context) { | |
int cnt = 0; | |
Object[] mapArr = new Object[this.size() * 2]; | |
for (Map.Entry<K, V> entry : this.entrySet()) { | |
mapArr[cnt] = entry.getKey(); | |
Object value = entry.getValue(); | |
if (value instanceof StateHolder || | |
value instanceof List || | |
!(value instanceof Serializable)) { | |
mapArr[cnt + 1] = UIComponentBase.saveAttachedState(context, value); | |
} else { | |
mapArr[cnt + 1] = value; | |
} | |
cnt += 2; | |
} | |
return mapArr; | |
} | |
} | |
/** | |
* Map used to keep track of list changes | |
*/ | |
@SuppressWarnings("serial") | |
static class InternalDeltaListMap<K, V> extends InternalMap<K, V> { | |
public InternalDeltaListMap() { | |
super(); | |
} | |
public InternalDeltaListMap(int initialCapacity, float loadFactor) { | |
super(initialCapacity, loadFactor); | |
} | |
public InternalDeltaListMap(int initialSize) { | |
super(initialSize); | |
} | |
public InternalDeltaListMap(Map<? extends K, ? extends V> m) { | |
super(m); | |
} | |
} | |
@SuppressWarnings("serial") | |
static class InternalList<T> extends ArrayList<T> implements StateHolder { | |
public InternalList() { | |
super(); | |
} | |
public InternalList(Collection<? extends T> c) { | |
super(c); | |
} | |
public InternalList(int initialSize) { | |
super(initialSize); | |
} | |
public boolean isTransient() { | |
return false; | |
} | |
public void setTransient(boolean newTransientValue) { | |
} | |
@SuppressWarnings("unchecked") | |
public void restoreState(FacesContext context, Object state) { | |
Object[] listAsArr = (Object[]) state; | |
//since all other options would mean dual iteration | |
//we have to do it the hard way | |
for (Object elem : listAsArr) { | |
add((T) UIComponentBase.restoreAttachedState(context, elem)); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
public Object saveState(FacesContext context) { | |
Object[] values = new Object[size()]; | |
for (int i = 0; i < size(); i++) { | |
Object value = get(i); | |
if (value instanceof StateHolder || | |
value instanceof List || | |
!(value instanceof Serializable)) { | |
values[i] = UIComponentBase.saveAttachedState(context, value); | |
} else { | |
values[i] = value; | |
} | |
} | |
return values; | |
} | |
} | |
} |