Abstract Component: UIIterate
A year and half ago, when I tried to create some complex UI component, I found JSF API missing a abstract component called UIIterate.
Without "for loop" data structure, we can only use foreach tag from JSTL. However, this customer tag iteration only applies in CreateView time. After this time, iteration is ignored and there is no way to make the iteration dynamically paticipate in all JSF lifecycle, in other word, we cannot change iteration through ValueBinding language. Furthermore, foreach tag is not a NamingContainer, so that component client id get duplicated in foreach tag loop.
We need UIIterate in many ways, such as JSF ForEach component to iteration java collections or array, Tree component to iterate TreeNode, DataTable to iterate records and dynamic columns(such as dynamic columns based on SQL "select * from xxx").
I waited for the JSF 1.2 specification, but this wasn't covered yet. Of course I can use my own UIIterate in my set of JSF components design. However, if there are many third party components which have defined their own UIIterate, they wouldn't recognize each other. The result is component collaboration has been weakened.
I am still waiting the standard API for this from Sun.
Following is my version of UIIterate:
package com.mycompany.jsf.ui.component;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;
import com.mycompany.jsf.ui.event.IterateEvent;
import com.mycompany.jsf.ui.util.AutoLinkedMap;
publicabstractclass UIIterateextends UIComponentBaseimplements NamingContainer{
public UIIterate(){
saved =new HashMap<String, SavedState>();
updates =new AutoLinkedMap();
}
publicboolean getRendersChildren(){
returntrue;
}
public String getClientId(FacesContext context){
if (context ==null)
thrownew NullPointerException();
String baseClientId = super.getClientId(context);
Object key = getKey(currentValue);
if (key !=null){
return baseClientId +':' + key.toString();
}
else{
return baseClientId;
}
}
publicvoid restoreState(FacesContext context, Object state){
Object values[] = (Object[]) state;
super.restoreState(context, values[0]);
this.saved = (Map)values[1];
this.updates = (Map)values[2];
this.currentValue = values[3];
this.value = values[4];
this.var = (String)values[5];
}
public Object saveState(FacesContext context){
Object values[] =new Object[6];
values[0] = super.saveState(context);
values[1] = saved;
values[2] = updates;
values[3] = currentValue;
values[4] = value;
values[5] = var;
return values;
}
publicvoid setValueBinding(String name, ValueBinding binding){
if ("var".equals(name))
thrownew IllegalArgumentException();
super.setValueBinding(name, binding);
}
publicvoid queueEvent(FacesEvent event){
super.queueEvent(new IterateEvent(this, event, currentValue));
}
publicvoid broadcast(FacesEvent event)throws AbortProcessingException{
if (!(eventinstanceof IterateEvent)){
super.broadcast(event);
return;
}else{
IterateEvent iEvent = (IterateEvent) event;
Object oldValue = currentValue;
setCurrentValue(iEvent.getCurrentValue());
FacesEvent facesEvent = iEvent.getFacesEvent();
facesEvent.getComponent().broadcast(facesEvent);
setCurrentValue(oldValue);
return;
}
}
publicvoid encodeBegin(FacesContext context)throws IOException{
currentValue =null;
if (!keepSaved(context))
saved =new HashMap<String, SavedState>();
super.encodeBegin(context);
}
protectedabstractvoid beforeProcessDecodes(FacesContext context);
publicvoid processDecodes(FacesContext context){
beforeProcessDecodes(context);
// start of processDecodes
if (context ==null)
thrownew NullPointerException();
if (!isRendered())
return;
currentValue =null;
if (null == saved || !keepSaved(context))
saved =new HashMap<String, SavedState>();
iterate(context, PhaseId.APPLY_REQUEST_VALUES);
decode(context);
afterProcessDecodes(context);
}
protectedabstractvoid afterProcessDecodes(FacesContext context);
protectedabstractvoid beforeProcessValidators(FacesContext context);
publicvoid processValidators(FacesContext context){
beforeProcessValidators(context);
if (context ==null)
thrownew NullPointerException();
if (!isRendered())
return;
if (isIterateNested())
currentValue =null;
iterate(context, PhaseId.PROCESS_VALIDATIONS);
afterProcessValidators(context);
}
protectedabstractvoid afterProcessValidators(FacesContext context);
protectedabstractvoid beforeProcessUpdates(FacesContext context);
publicvoid processUpdates(FacesContext context){
beforeProcessUpdates(context);
if (context ==null)
thrownew NullPointerException();
if (!isRendered())
return;
if (isIterateNested())
currentValue =null;
iterate(context, PhaseId.UPDATE_MODEL_VALUES);
afterProcessUpdates(context);
}
protectedabstractvoid afterProcessUpdates(FacesContext context);
protectedvoid iterate(FacesContext context, PhaseId phaseId){
for(Iterator i = iterator(); i.hasNext(); ){
i.next();
for(Iterator kids = getFacetsAndChildren(); kids.hasNext(); ){
processPhase(context, (UIComponent) kids.next(), phaseId);
}
}
}
publicabstract Iterator iterator();
protectedabstract Object getKey(Object value);
protectedvoid processPhase(FacesContext context, UIComponent component, PhaseId phaseId){
if (component.isRendered())
if (phaseId == PhaseId.APPLY_REQUEST_VALUES)
component.processDecodes(context);
elseif (phaseId == PhaseId.PROCESS_VALIDATIONS)
component.processValidators(context);
elseif (phaseId == PhaseId.UPDATE_MODEL_VALUES)
component.processUpdates(context);
else
thrownew IllegalArgumentException();
}
protectedboolean keepSaved(FacesContext context){
for(Iterator clientIds = saved.keySet().iterator(); clientIds.hasNext(); ){
String clientId = (String) clientIds.next();
for(Iterator messages = context.getMessages(clientId); messages.hasNext(); ){
FacesMessage message = (FacesMessage) messages.next();
if(message.getSeverity().compareTo(
FacesMessage.SEVERITY_ERROR) >= 0)returntrue;
}
}
return isIterateNested();
}
protectedboolean isIterateNested(){
for (UIComponent parent =this;null != (parent = parent.getParent());)
if (parentinstanceof UIIterate)
returntrue;
returnfalse;
}
protectedvoid restoreDescendantState(){
FacesContext context = getFacesContext();
Iterator kids = getFacetsAndChildren();
while(kids.hasNext()){
UIComponent kid = (UIComponent) kids.next();
restoreDescendantState(kid, context);
}
}
protectedvoid restoreDescendantState(UIComponent component,
FacesContext context){
String id = component.getId();
component.setId(id);
if (componentinstanceof EditableValueHolder){
EditableValueHolder input = (EditableValueHolder) component;
String clientId = component.getClientId(context);
SavedState state = (SavedState) saved.get(clientId);
if (state ==null)
state =new SavedState();
input.setValue(state.getValue());
input.setValid(state.isValid());
input.setSubmittedValue(state.getSubmittedValue());
input.setLocalValueSet(state.isLocalValueSet());
}
for (Iterator kids = component.getChildren().iterator(); kids.hasNext(); restoreDescendantState(
(UIComponent) kids.next(), context))
;
}
protectedvoid saveDescendantState(){
FacesContext context = getFacesContext();
Iterator kids = getFacetsAndChildren();
while(kids.hasNext()){
UIComponent kid = (UIComponent) kids.next();
saveDescendantState(kid, context);
}
}
protectedvoid saveDescendantState(UIComponent component, FacesContext context){
if (componentinstanceof EditableValueHolder){
EditableValueHolder input = (EditableValueHolder) component;
String clientId = component.getClientId(context);
SavedState state = (SavedState) saved.get(clientId);
if (state ==null){
state =new SavedState();
saved.put(clientId, state);
}
state.setValue(input.getLocalValue());
state.setValid(input.isValid());
state.setSubmittedValue(input.getSubmittedValue());
state.setLocalValueSet(input.isLocalValueSet());
}
for (Iterator kids = component.getChildren().iterator(); kids.hasNext(); saveDescendantState(
(UIComponent) kids.next(), context))
;
}
public Object getValue(){
if (value !=null)
return value;
ValueBinding vb = getValueBinding("value");
if (vb !=null)
return vb.getValue(getFacesContext());
else
returnnull;
}
publicvoid setValue(Object value){
this.value = value;
}
public String getVar(){
return var;
}
publicvoid setVar(String var){
this.var = var;
}
public Map getUpdates(){
return updates;
}
publicvoid clearStateHolder(){
saved.clear();
}
publicvoid clearUpdates(){
updates.clear();
}
public Object getCurrentValue()
{
return currentValue;
}
publicvoid setCurrentValue(Object currentValue)
{
saveDescendantState();
this.currentValue = currentValue;
if(getVar() !=null){
Map<Object, Object> requestMap = getFacesContext().getExternalContext().getRequestMap();
requestMap.put(getVar(), currentValue);
if(currentValue !=null)requestMap.put("updates", updates.get(getKey(currentValue)));
}
restoreDescendantState();
}
protected Object value;
protected String var;
protected Map<String, SavedState> saved;
protected Object currentValue;
protected Map updates;
protectedclass UIIteratorimplements Iterator{
private Iterator i;
public UIIterator(Iterator i){
setCurrentValue(null);
this.i = i;
}
publicboolean hasNext(){
boolean result = i.hasNext();
if(!result)setCurrentValue(null);
return result;
}
public Object next(){
Object item = i.next();
setCurrentValue(item);
return item;
}
publicvoid remove(){
setCurrentValue(null);
i.remove();
}
}
}
package com.mycompany.jsf.ui.event;
import javax.faces.component.UIComponent;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
publicclass IterateEventextends FacesEvent{
public IterateEvent(UIComponent component, FacesEvent event, Object currentValue){
super(component);
this.event = event;
this.currentValue = currentValue;
}
public FacesEvent getFacesEvent(){
return event;
}
public Object getCurrentValue(){
return currentValue;
}
public PhaseId getPhaseId(){
return event.getPhaseId();
}
publicvoid setPhaseId(PhaseId phaseId){
event.setPhaseId(phaseId);
}
publicboolean isAppropriateListener(FacesListener listener){
returnfalse;
}
publicvoid processListener(FacesListener listener){
thrownew IllegalStateException();
}
private FacesEvent event;
private Object currentValue;
}
package com.mycompany.jsf.ui.util;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Vector;
publicclass AutoLinkedMap<K, V>extends LinkedHashMap<K, V>{
public AutoLinkedMap(){
}
public AutoLinkedMap(Class<V> clazz){
this.instanceClass = clazz;
}
public V get(Object key){
V value = super.get(key);
if(value ==null && !isStop()){
value = (V)createInstance();
super.put((K)key, value);
}
return value;
}
public V put(K key, V value){
V oldValue = super.put(key, value);
if(!((oldValue==null && value==null) || valueinstanceof AutoLinkedMap || (oldValue!=null && oldValue.toString().equals(value.toString())))){
PropertyChangeEvent vce =new PropertyChangeEvent(key, key.toString(), oldValue, value);
for(PropertyChangeListener listener : propertyChangeListeners){
listener.propertyChange(vce);
}
}
return oldValue;
}
publicboolean containsKey(Object key){
returntrue;
}
publicboolean realContainsKey(Object key){
return super.containsKey(key);
}
publicvoid addPropertyChangeListener(PropertyChangeListener listener){
if(listener!=null)propertyChangeListeners.add(listener);
}
public String toString(){
return super.toString();
}
private V createInstance(){
if(instanceClass==null)return (V)new AutoLinkedMap();
if(Boolean.class.equals(instanceClass) || Boolean.TYPE.equals(instanceClass))return (V)new Boolean(false);
elseif(Integer.class.equals(instanceClass) || Integer.TYPE.equals(instanceClass))return (V)new Integer(0);
elseif(Long.class.equals(instanceClass) || Long.TYPE.equals(instanceClass))return (V)new Long(0l);
elseif(Short.class.equals(instanceClass) || Short.TYPE.equals(instanceClass))return (V)new Short((short)0);
elseif(Byte.class.equals(instanceClass) || Byte.TYPE.equals(instanceClass))return (V)new Byte((byte)0);
elseif(Float.class.equals(instanceClass) || Float.TYPE.equals(instanceClass))return (V)new Float(0.0f);
elseif(Double.class.equals(instanceClass) || Double.TYPE.equals(instanceClass))return (V)new Double(0.0d);
elseif(Character.class.equals(instanceClass) || Character.TYPE.equals(instanceClass))return (V)new Character((char)0);
elsetry{
return instanceClass.newInstance();
}catch (Exception e){
returnnull;
}
}
publicboolean isStop(){
return stop;
}
publicvoid setStop(boolean stop){
this.stop = stop;
}
private Class<V> instanceClass;
private List<PropertyChangeListener> propertyChangeListeners =new Vector<PropertyChangeListener>();
privateboolean stop;
}

