/*
* Copyright 2013 GiavaCms.org.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.giavacms.common.renderer;
import javax.faces.FacesException;
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.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import javax.faces.model.ArrayDataModel;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.ResultSetDataModel;
import javax.faces.model.ScalarDataModel;
import javax.faces.render.Renderer;
import java.io.IOException;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* (c) 2009 skiline AG
* <p>
*
* Adds a varStatus feature to the UIRepeat tag delivered with facelets. The var-status contains the following fields:
*
* index ... the current index (zero based) count ... the iteration count (number of iteration, which means: the size of
* the collection) first ... true, if the current iteration is the first (true, if index==0) last ... true, if the
* current iteration is the last one (true, if index==count-1)
*
* If you need an iteration without a collection, you can use from and to instead of value!
*
* @author $Author$
* @version $Revision$ $Date$
*/
@SuppressWarnings({ "deprecation", "unchecked", "unused", "rawtypes" })
public class UIRepeat extends UIComponentBase implements NamingContainer
{
public static final String COMPONENT_TYPE = "facelets.ui.Repeat";
public static final String COMPONENT_FAMILY = "facelets";
private final static DataModel EMPTY_MODEL = new ListDataModel(
Collections.EMPTY_LIST);
// our data
private Object value;
private Object from;
private Object to;
private transient DataModel model;
// variables
private String var;
private String varStatus;
private int index = -1;
// scoping
private int offset = -1;
private int size = -1;
private Status status;
public UIRepeat()
{
this.setRendererType("facelets.ui.Repeat");
}
public String getFamily()
{
return COMPONENT_FAMILY;
}
public int getOffset()
{
if (this.offset != -1)
{
return this.offset;
}
ValueBinding vb = this.getValueBinding("offset");
if (vb != null)
{
return ((Integer) vb.getValue(FacesContext.getCurrentInstance()))
.intValue();
}
return 0;
}
public void setOffset(int offset)
{
this.offset = offset;
}
public int getSize()
{
if (this.size != -1)
{
return this.size;
}
ValueBinding vb = this.getValueBinding("size");
if (vb != null)
{
return ((Integer) vb.getValue(FacesContext.getCurrentInstance()))
.intValue();
}
return -1;
}
public void setSize(int size)
{
this.size = size;
}
public String getVar()
{
return this.var;
}
public void setVar(String var)
{
this.var = var;
}
private void resetDataModel()
{
if (this.isNestedInIterator())
{
this.setDataModel(null);
}
}
private synchronized void setDataModel(DataModel model)
{
this.model = model;
}
private synchronized DataModel getDataModel()
{
if (this.model == null)
{
Object val = this.getValue();
if (val == null)
{
int from = getFromValue();
int to = getToValue();
if (from <= to)
{
// create a list, which contains the loop values
List list = new ArrayList();
for (int i = from; i <= to; i++)
{
list.add(i);
}
this.model = new ListDataModel(list);
}
else
{
this.model = EMPTY_MODEL;
}
}
else if (val instanceof DataModel)
{
this.model = (DataModel) val;
}
else if (val instanceof List)
{
this.model = new ListDataModel((List) val);
}
else if (Object[].class.isAssignableFrom(val.getClass()))
{
this.model = new ArrayDataModel((Object[]) val);
}
else if (val instanceof ResultSet)
{
this.model = new ResultSetDataModel((ResultSet) val);
}
else
{
this.model = new ScalarDataModel(val);
}
}
status = new Status(model.getRowCount());
return this.model;
}
public Object getValue()
{
if (this.value == null)
{
ValueBinding vb = this.getValueBinding("value");
if (vb != null)
{
return vb.getValue(FacesContext.getCurrentInstance());
}
}
return this.value;
}
public void setValue(Object value)
{
this.value = value;
}
private int getFromValue()
{
Object from = this.from;
if (from == null)
{
ValueBinding vb = this.getValueBinding("from");
if (vb != null)
{
from = vb.getValue(FacesContext.getCurrentInstance());
}
}
if (from instanceof Number)
{
return ((Number) from).intValue();
}
else
{
return 0;
}
}
private int getToValue()
{
Object to = this.to;
if (to == null)
{
ValueBinding vb = this.getValueBinding("to");
if (vb != null)
{
to = vb.getValue(FacesContext.getCurrentInstance());
}
}
if (to instanceof Number)
{
return ((Number) to).intValue();
}
else
{
return -1;
}
}
private transient StringBuffer buffer;
private StringBuffer getBuffer()
{
if (this.buffer == null)
{
this.buffer = new StringBuffer();
}
this.buffer.setLength(0);
return this.buffer;
}
public String getClientId(FacesContext faces)
{
String id = super.getClientId(faces);
if (this.index >= 0)
{
id = this.getBuffer().append(id).append(
NamingContainer.SEPARATOR_CHAR).append(this.index)
.toString();
}
return id;
}
private transient Object origValue;
private transient Object origStatus;
private void captureOrigValue()
{
FacesContext faces = FacesContext.getCurrentInstance();
Map attrs = faces.getExternalContext().getRequestMap();
if (this.var != null)
{
this.origValue = attrs.get(this.var);
}
if (this.varStatus != null)
{
this.origStatus = attrs.get(this.varStatus);
}
}
private void restoreOrigValue()
{
FacesContext faces = FacesContext.getCurrentInstance();
Map attrs = faces.getExternalContext().getRequestMap();
if (this.var != null)
{
if (this.origValue != null)
{
attrs.put(this.var, this.origValue);
}
else
{
attrs.remove(this.var);
}
}
if (this.varStatus != null)
{
if (this.origStatus != null)
{
attrs.put(varStatus, origStatus);
}
else
{
attrs.remove(this.varStatus);
}
}
}
private Map childState;
private Map getChildState()
{
if (this.childState == null)
{
this.childState = new HashMap();
}
return this.childState;
}
private void saveChildState()
{
if (this.getChildCount() > 0)
{
FacesContext faces = FacesContext.getCurrentInstance();
Iterator itr = this.getChildren().iterator();
while (itr.hasNext())
{
this.saveChildState(faces, (UIComponent) itr.next());
}
}
}
private void saveChildState(FacesContext faces, UIComponent c)
{
if (c instanceof EditableValueHolder && !c.isTransient())
{
String clientId = c.getClientId(faces);
SavedState ss = (SavedState) this.getChildState().get(clientId);
if (ss == null)
{
ss = new SavedState();
this.getChildState().put(clientId, ss);
}
ss.populate((EditableValueHolder) c);
}
// continue hack
Iterator itr = c.getFacetsAndChildren();
while (itr.hasNext())
{
saveChildState(faces, (UIComponent) itr.next());
}
}
private void restoreChildState()
{
if (this.getChildCount() > 0)
{
FacesContext faces = FacesContext.getCurrentInstance();
Iterator itr = this.getChildren().iterator();
while (itr.hasNext())
{
this.restoreChildState(faces, (UIComponent) itr.next());
}
}
}
private void restoreChildState(FacesContext faces, UIComponent c)
{
// reset id
String id = c.getId();
c.setId(id);
// hack
if (c instanceof EditableValueHolder)
{
EditableValueHolder evh = (EditableValueHolder) c;
String clientId = c.getClientId(faces);
SavedState ss = (SavedState) this.getChildState().get(clientId);
if (ss != null)
{
ss.apply(evh);
}
else
{
NullState.apply(evh);
}
}
// continue hack
Iterator itr = c.getFacetsAndChildren();
while (itr.hasNext())
{
restoreChildState(faces, (UIComponent) itr.next());
}
}
private boolean keepSaved(FacesContext context)
{
Iterator clientIds = this.getChildState().keySet().iterator();
while (clientIds.hasNext())
{
String clientId = (String) clientIds.next();
Iterator messages = context.getMessages(clientId);
while (messages.hasNext())
{
FacesMessage message = (FacesMessage) messages.next();
if (message.getSeverity()
.compareTo(FacesMessage.SEVERITY_ERROR) >= 0)
{
return (true);
}
}
}
return (isNestedInIterator());
}
private boolean isNestedInIterator()
{
UIComponent parent = this.getParent();
while (parent != null)
{
if (parent instanceof UIData || parent instanceof UIRepeat)
{
return true;
}
parent = parent.getParent();
}
return false;
}
private void setIndex(int index)
{
// save child state
this.saveChildState();
this.index = index;
DataModel localModel = getDataModel();
localModel.setRowIndex(index);
if (this.index != -1 && this.var != null && localModel.isRowAvailable())
{
FacesContext faces = FacesContext.getCurrentInstance();
Map attrs = faces.getExternalContext().getRequestMap();
Object rowData = localModel.getRowData();
attrs.put(var, rowData);
if (varStatus != null)
{
attrs.put(varStatus, status);
}
}
// restore child state
this.restoreChildState();
}
private boolean isIndexAvailable()
{
return this.getDataModel().isRowAvailable();
}
public void process(FacesContext faces, PhaseId phase)
{
// stop if not rendered
if (!this.isRendered())
return;
// clear datamodel
this.resetDataModel();
// reset index
this.captureOrigValue();
this.setIndex(-1);
try
{
// has children
if (this.getChildCount() > 0)
{
Iterator itr;
UIComponent c;
int i = this.getOffset();
int end = this.getSize();
end = (end >= 0) ? i + end : Integer.MAX_VALUE - 1;
// grab renderer
String rendererType = getRendererType();
Renderer renderer = null;
if (rendererType != null)
{
renderer = getRenderer(faces);
}
this.setIndex(i);
while (i <= end && this.isIndexAvailable())
{
if (PhaseId.RENDER_RESPONSE.equals(phase)
&& renderer != null)
{
renderer.encodeChildren(faces, this);
}
else
{
itr = this.getChildren().iterator();
while (itr.hasNext())
{
c = (UIComponent) itr.next();
if (PhaseId.APPLY_REQUEST_VALUES.equals(phase))
{
c.processDecodes(faces);
}
else if (PhaseId.PROCESS_VALIDATIONS
.equals(phase))
{
c.processValidators(faces);
}
else if (PhaseId.UPDATE_MODEL_VALUES
.equals(phase))
{
c.processUpdates(faces);
}
else if (PhaseId.RENDER_RESPONSE.equals(phase))
{
c.encodeAll(faces);
}
}
}
i++;
this.setIndex(i);
status.next();
}
}
}
catch (IOException e)
{
throw new FacesException(e);
}
finally
{
this.setIndex(-1);
this.restoreOrigValue();
}
}
public void processDecodes(FacesContext faces)
{
if (!this.isRendered())
return;
this.setDataModel(null);
if (!this.keepSaved(faces))
this.childState = null;
this.process(faces, PhaseId.APPLY_REQUEST_VALUES);
this.decode(faces);
}
public void processUpdates(FacesContext faces)
{
if (!this.isRendered())
return;
this.resetDataModel();
this.process(faces, PhaseId.UPDATE_MODEL_VALUES);
}
public void processValidators(FacesContext faces)
{
if (!this.isRendered())
return;
this.resetDataModel();
this.process(faces, PhaseId.PROCESS_VALIDATIONS);
}
private final static SavedState NullState = new SavedState();
// from RI
private final static class SavedState implements Serializable
{
private Object submittedValue;
private static final long serialVersionUID = 2920252657338389849L;
Object getSubmittedValue()
{
return (this.submittedValue);
}
void setSubmittedValue(Object submittedValue)
{
this.submittedValue = submittedValue;
}
private boolean valid = true;
boolean isValid()
{
return (this.valid);
}
void setValid(boolean valid)
{
this.valid = valid;
}
private Object value;
Object getValue()
{
return (this.value);
}
public void setValue(Object value)
{
this.value = value;
}
private boolean localValueSet;
boolean isLocalValueSet()
{
return (this.localValueSet);
}
public void setLocalValueSet(boolean localValueSet)
{
this.localValueSet = localValueSet;
}
public String toString()
{
return ("submittedValue: " + submittedValue + " value: " + value
+ " localValueSet: " + localValueSet);
}
public void populate(EditableValueHolder evh)
{
this.value = evh.getValue();
this.valid = evh.isValid();
this.submittedValue = evh.getSubmittedValue();
this.localValueSet = evh.isLocalValueSet();
}
public void apply(EditableValueHolder evh)
{
evh.setValue(this.value);
evh.setValid(this.valid);
evh.setSubmittedValue(this.submittedValue);
evh.setLocalValueSet(this.localValueSet);
}
}
public static class Status
{
private int index;
private int count;
private Status(int count)
{
this.count = count;
}
public int getIndex()
{
return index;
}
public int getCount()
{
return count;
}
public boolean isFirst()
{
return index == 0;
}
public boolean isLast()
{
return index == count - 1;
}
public void setIndex(int index)
{
this.index = index;
}
public void setCount(int count)
{
this.count = count;
}
public void next()
{
index++;
}
public String toString()
{
return "UIRepeat$Status[count=" + count + ",index=" + index
+ ",first=" + isFirst() + ",last=" + isLast() + "]";
}
}
private final class IndexedEvent extends FacesEvent
{
private static final long serialVersionUID = 5475426608254583480L;
private final FacesEvent target;
private final int index;
public IndexedEvent(UIRepeat owner, FacesEvent target, int index)
{
super(owner);
this.target = target;
this.index = index;
}
public PhaseId getPhaseId()
{
return (this.target.getPhaseId());
}
public void setPhaseId(PhaseId phaseId)
{
this.target.setPhaseId(phaseId);
}
public boolean isAppropriateListener(FacesListener listener)
{
return this.target.isAppropriateListener(listener);
}
public void processListener(FacesListener listener)
{
UIRepeat owner = (UIRepeat) this.getComponent();
int prevIndex = owner.index;
try
{
owner.setIndex(this.index);
if (owner.isIndexAvailable())
{
this.target.processListener(listener);
}
}
finally
{
owner.setIndex(prevIndex);
}
}
public int getIndex()
{
return index;
}
public FacesEvent getTarget()
{
return target;
}
}
public void broadcast(FacesEvent event) throws AbortProcessingException
{
if (event instanceof IndexedEvent)
{
IndexedEvent idxEvent = (IndexedEvent) event;
this.resetDataModel();
int prevIndex = this.index;
try
{
this.setIndex(idxEvent.getIndex());
if (this.isIndexAvailable())
{
FacesEvent target = idxEvent.getTarget();
target.getComponent().broadcast(target);
}
}
finally
{
this.setIndex(prevIndex);
}
}
else
{
super.broadcast(event);
}
}
public void queueEvent(FacesEvent event)
{
super.queueEvent(new IndexedEvent(this, event, this.index));
}
public void restoreState(FacesContext faces, Object object)
{
Object[] state = (Object[]) object;
super.restoreState(faces, state[0]);
this.childState = (Map) state[1];
this.offset = ((Integer) state[2]).intValue();
this.size = ((Integer) state[3]).intValue();
this.var = (String) state[4];
this.value = state[5];
}
public Object saveState(FacesContext faces)
{
Object[] state = new Object[6];
state[0] = super.saveState(faces);
state[1] = this.childState;
state[2] = new Integer(this.offset);
state[3] = new Integer(this.size);
state[4] = this.var;
state[5] = this.value;
return state;
}
public void encodeChildren(FacesContext faces) throws IOException
{
if (!isRendered())
{
return;
}
this.setDataModel(null);
if (!this.keepSaved(faces))
{
this.childState = null;
}
this.process(faces, PhaseId.RENDER_RESPONSE);
}
public boolean getRendersChildren()
{
Renderer renderer = null;
if (getRendererType() != null)
{
if (null != (renderer = getRenderer(getFacesContext())))
{
return renderer.getRendersChildren();
}
}
return true;
}
public String getVarStatus()
{
return varStatus;
}
public void setVarStatus(String varStatus)
{
this.varStatus = varStatus;
}
public Object getFrom()
{
return from;
}
public void setFrom(Object from)
{
this.from = from;
}
public Object getTo()
{
return to;
}
public void setTo(Object to)
{
this.to = to;
}
}