package org.richfaces.taglib;
import java.beans.FeatureDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import javax.el.FunctionMapper;
import javax.el.MethodExpression;
import javax.el.MethodInfo;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.webapp.UIComponentClassicTagBase;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.IterationTag;
import org.richfaces.component.UIColumn;
import org.richfaces.component.html.HtmlColumn;
import org.richfaces.iterator.ForEachIterator;
import org.richfaces.iterator.SimpleForEachIterator;
import org.richfaces.renderkit.CellRenderer;
@SuppressWarnings("unused")
public class ColumnsTag extends UIComponentClassicTagBase implements
IterationTag {
/** Data table */
private UIComponent dataTable;
/** Prefix before id to be assigned for column */
private static final String COLUMN_ID_PREFIX = "rf";
/** Flag indicates if columns are already created */
private boolean created = false;
/** Current column counter */
private Integer index = -1;
/** Iterator for columns's tag value attribute */
protected ForEachIterator items; // our 'digested' items
/** Value attribute value */
protected Object rawItems; // our 'raw' items
/** End attribute - defines count of column if value attr hasn't been defined */
private ValueExpression __columns;
/** Begin attribute - defines the first iteration item */
private ValueExpression __begin;
/** Begin attribute - defines the last iteration item */
private ValueExpression __end;
/** Index attr - defines page variable for current column counter */
private ValueExpression _index;
/** Var attr - defines page variable for current item */
private String indexId;
/** Integer value of end attr. */
private Integer columns;
/** Integer value of begin attr. */
private Integer begin;
/** Integer value of end attr. */
private Integer end;
/** String value of var attr */
private String itemId = null;
/** Expression for var item */
private IteratedExpression iteratedExpression;
/** Column incrementer */
private Integer counter = 0;
/**
* style CSS style(s) is/are to be applied when this component is rendered
*/
private ValueExpression _style;
/*
* sortBy Attribute defines a bean property which is used for sorting of a
* column
*/
private ValueExpression _sortBy;
/**
* sortOrder SortOrder is an enumeration of the possible sort orderings.
*/
private ValueExpression _sortOrder;
/**
* value The current value for this component
*/
private ValueExpression __value;
/**
* var A request-scope attribute via which the data object for the current
* row will be used when iterating
*/
private ValueExpression __var;
/**
* breakBefore if "true" next column begins from the first row
*/
private ValueExpression _breakBefore;
/**
* colspan Corresponds to the HTML colspan attribute
*/
private ValueExpression _colspan;
/**
* Dir attr
*/
private ValueExpression _dir;
/*
* filterDefaultLabel
*
*/
private ValueExpression _filterDefaultLabel;
/**
* comparator
*
*/
private ValueExpression _comparator;
/*
* filterEvent Event for filter input that forces the filtration (default =
* onchange)
*/
private ValueExpression _filterEvent;
/*
* filterExpression Attribute defines a bean property which is used for
* filtering of a column
*/
private ValueExpression _filterExpression;
/*
* filterMethod
*
*/
private MethodExpression _filterMethod;
/*
* filterValue
*
*/
private ValueExpression _filterValue;
/*
* filterBy
*
*/
private ValueExpression _filterBy;
/**
* footerClass Space-separated list of CSS style class(es) that are be
* applied to any footer generated for this table
*/
private ValueExpression _footerClass;
/**
* headerClass Space-separated list of CSS style class(es) that are be
* applied to any header generated for this table
*/
private ValueExpression _headerClass;
/**
* rowspan Corresponds to the HTML rowspan attribute
*/
private ValueExpression _rowspan;
/**
* selfSorted
*
*/
private ValueExpression _selfSorted;
/**
* sortable Boolean attribute. If "true" it's possible to sort the column
* content after click on the header. Default value is "true"
*/
private ValueExpression _sortable;
/**
* sortExpression Attribute defines a bean property which is used for
* sorting of a column
*/
private ValueExpression _sortExpression;
/**
* styleClass Corresponds to the HTML class attribute
*/
private ValueExpression _styleClass;
/*
* width Attribute defines width of column. Default value is "100px".
*/
private ValueExpression _width;
/**
* SortOrder is an enumeration of the possible sort orderings. Setter for
* sortOrder
*
* @param sortOrder -
* new value
*/
public void setSortOrder(ValueExpression __sortOrder) {
this._sortOrder = __sortOrder;
}
/**
* Attribute defines a bean property which is used for sorting of a column
* Setter for sortBy
*
* @param sortBy -
* new value
*/
public void setSortBy(ValueExpression __sortBy) {
this._sortBy = __sortBy;
}
/**
* CSS style(s) is/are to be applied when this component is rendered Setter
* for style
*
* @param style -
* new value
*/
public void setStyle(ValueExpression __style) {
this._style = __style;
}
/*
* (non-Javadoc)
*
* @see javax.faces.webapp.UIComponentClassicTagBase#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
prepare();
if (hasNext()) {
next();
exposeVariables();
super.doStartTag();
} else {
return SKIP_BODY;
}
return EVAL_BODY_BUFFERED;
}
/*
* (non-Javadoc)
*
* @see javax.faces.webapp.UIComponentClassicTagBase#doAfterBody()
*/
@Override
public int doAfterBody() throws JspException {
try {
if (hasNext()) {
super.doAfterBody();
super.doEndTag();
next();
exposeVariables();
super.doStartTag();
return EVAL_BODY_AGAIN;
} else {
super.doAfterBody();
exposeVariables();
return EVAL_BODY_INCLUDE;
}
} catch (Exception e) {
throw new JspException(e);
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.webapp.UIComponentClassicTagBase#doEndTag()
*/
@Override
public int doEndTag() throws JspException {
if (!atFirst()) {
return super.doEndTag();
}
return EVAL_PAGE;
}
/*
* (non-Javadoc)
*
* @see javax.faces.webapp.UIComponentClassicTagBase#doInitBody()
*/
@Override
public void doInitBody() throws JspException {
}
@Override
protected UIComponent createComponent(FacesContext context, String newId)
throws JspException {
UIComponent c = getFacesContext().getApplication().createComponent(
getComponentType());
c.setId(generateColumnId());
c.setTransient(false);
setProperties(c);
return c;
}
@Override
protected boolean hasBinding() {
return false;
}
@Override
protected void setProperties(UIComponent component) {
ELContext elContext = getContext(pageContext.getELContext());
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
Object o = field.get(this);
if (o != null && o instanceof ValueExpression) {
String fieldName = field.getName();
if (fieldName != null && fieldName.startsWith("_")) {
String attributeName = fieldName.replace("_", "");
ValueExpression ex = (ValueExpression) o;
ex = createValueExpression(elContext, ex);
component.setValueExpression(attributeName, ex);
}
}
} catch (Exception e) {
continue;
}
}
// Set filterMethod attribute
if (_filterMethod != null) {
MethodExpression mexpr = getFacesContext().getApplication()
.getExpressionFactory().createMethodExpression(elContext,
_filterMethod.getExpressionString(), Object.class,
new Class[] { Object.class });
((HtmlColumn) component).setFilterMethod(mexpr);
}
}
/**
* Creates value expression to be out into column
*
* @param context
* @param orig
* @return
*/
private ValueExpression createValueExpression(ELContext context,
ValueExpression orig) {
ValueExpression vexpr = getFacesContext().getApplication()
.getExpressionFactory().createValueExpression(context,
orig.getExpressionString(), orig.getExpectedType());
return vexpr;
}
/**
* Deletes dynamic rich columns created before
*/
private void deleteRichColumns() {
List<UIComponent> children = dataTable.getChildren();
Iterator<UIComponent> it = children.iterator();
List<UIComponent> forDelete = new ArrayList<UIComponent>();
Integer i = 0;
while (it.hasNext()) {
UIComponent child = it.next();
String id = child.getId();
if (id != null && id.startsWith(COLUMN_ID_PREFIX)) {
forDelete.add(child);
}
i++;
}
it = forDelete.iterator();
while (it.hasNext()) {
UIComponent elem = it.next();
children.remove(elem);
}
}
/**
* Method prepares all we need for starting of tag rendering
*
* @throws JspTagException
*/
private void prepare() throws JspTagException {
dataTable = getParentUIComponentClassicTagBase(pageContext)
.getComponentInstance();
created = (dataTable.getChildCount() > 0);
if (created) {
deleteRichColumns();
}
initVariables();
// produce the right sort of ForEachIterator
if (__value != null) {
// If this is a deferred expression, make a note and get
// the 'items' instance.
if (__value instanceof ValueExpression) {
rawItems = __value.getValue(pageContext.getELContext());
}
// extract an iterator over the 'items' we've got
items = SimpleForEachIterator
.supportedTypeForEachIterator(rawItems);
} else {
// no 'items', so use 'begin' and 'end'
items = SimpleForEachIterator.beginEndForEachIterator(columns - 1);
}
correctFirst();
/*
* ResultSet no more supported in <c:forEach> // step must be 1 when
* ResultSet is passed in if (rawItems instanceof ResultSet && step !=
* 1) throw new JspTagException(
* Resources.getMessage("FOREACH_STEP_NO_RESULTSET"));
*/
}
/**
* Extracts tags attributes values
*/
private void initVariables() {
initColumnsCount();
initIndex();
initVar();
initBegin();
initEnd();
}
private void initItarationId() {
String jspId = getJspId();
if (jspId != null) {
setJspId(jspId); // We need it twice!
setJspId(jspId);
}
}
/**
* Extracts integer value from end attr
*/
private void initColumnsCount() {
if (__columns != null) {
if (__columns instanceof ValueExpression)
try {
String t = (String) __columns.getValue(getELContext());
columns = Integer.parseInt(t);
if (columns < 0) {
columns = 0; // If end is negative set up zero
}
} catch (Exception e) {
columns = 0;
}
} else {
columns = 0;
}
}
/**
* Extracts string value from var attr
*/
private void initVar() {
if (__var != null) {
try {
itemId = (String) __var.getValue(getELContext());
} catch (ClassCastException e) {
itemId = null;
}
}
}
/**
* Extracts string value from index attr
*/
private void initIndex() {
if (_index != null) {
try {
indexId = (String) _index.getValue(getELContext());
} catch (ClassCastException e) {
indexId = null;
}
}
}
/**
* Extracts string value from index attr
*/
private void initBegin() {
begin = 0;
if (__begin != null) {
try {
Object o = __begin.getValue(getELContext());
if (o instanceof Number) {
begin = ((Number) o).intValue();
} else if (o instanceof String) {
begin = Integer.parseInt((String) o);
}
begin--; // correct begin value
if (begin < 0) {
begin = 0;
}
} catch (ClassCastException e) {
begin = 0;
}
}
}
/**
* Extracts string value from index attr
*/
private void initEnd() {
end = 0;
if (__end != null) {
try {
Object o = __end.getValue(getELContext());
if (o instanceof Number) {
end = ((Number) o).intValue();
} else if (o instanceof String) {
end = Integer.parseInt((String) o);
}
if (end < 0) {
end = 0;
}
} catch (ClassCastException e) {
end = 0;
}
}
}
/**
* Inits first iteration item
*/
private void correctFirst() {
try {
if (items != null) {
if (begin > 0 && (index < (begin - 1))) {
while ((index < (begin - 1)) && hasNext()) {
next();
}
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
/**
* Return true if we didn't complete column's count
*
* @return
* @throws JspTagException
*/
private boolean hasNext() throws JspTagException {
if (end != 0) {
return (index < (end - 1)) ? items.hasNext() : false;
} else {
return items.hasNext();
}
}
/**
* Iterate to next column
*
* @return
* @throws JspTagException
*/
private Object next() throws JspTagException {
Object o = items.next();
initItarationId();
index++;
return o;
}
/**
* Returns true if this is the first loop of columns tag
*
* @return
*/
private boolean atFirst() {
return (index == begin - 1);
}
private String generateColumnId() {
return COLUMN_ID_PREFIX + Integer.toString(index);
}
/**
* Create custom context with VariableMapper override.
*
* @param context
* @return
*/
private ELContext getContext(final ELContext cont) {
return new ELContext() {
@Override
public Object getContext(Class key) {
return cont.getContext(key);
}
@Override
public ELResolver getELResolver() {
return cont.getELResolver();
}
@Override
public FunctionMapper getFunctionMapper() {
return cont.getFunctionMapper();
}
@Override
public VariableMapper getVariableMapper() {
return new VariableMapper() {
@Override
public ValueExpression resolveVariable(String variable) {
if (variable.equals(itemId)) {
return new IndexedValueExpression(__value, index);
} else if (variable.equals(indexId)) {
return new IteratedIndexExpression(index);
}
return cont.getVariableMapper().resolveVariable(
variable);
}
@Override
public ValueExpression setVariable(String variable,
ValueExpression expression) {
return cont.getVariableMapper().setVariable(variable,
expression);
}
};
}
};
}
@Override
public String getComponentType() {
return UIColumn.COMPONENT_TYPE;
}
@Override
public String getRendererType() {
return CellRenderer.class.getName();
}
/**
* Sets page request variables
*
* @throws JspTagException
*/
private void exposeVariables() throws JspTagException {
/*
* We need to support null items returned from next(); we do this simply
* by passing such non-items through to the scoped variable as
* effectively 'null' (that is, by calling removeAttribute()).
*
* Also, just to be defensive, we handle the case of a null 'status'
* object as well.
*
* We call getCurrent() and getLoopStatus() (instead of just using
* 'item' and 'status') to bridge to subclasses correctly. A subclass
* can override getCurrent() or getLoopStatus() but still depend on our
* doStartTag() and doAfterBody(), which call this method
* (exposeVariables()), to expose 'item' and 'status' correctly.
*/
// Set up var variable
if (itemId != null) {
if (index == null)
pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
else if (__value != null) {
VariableMapper vm = pageContext.getELContext()
.getVariableMapper();
if (vm != null) {
ValueExpression ve = getVarExpression(__value);
vm.setVariable(itemId, ve);
}
} else
pageContext.setAttribute(itemId, index);
}
// Set up index variable
if (indexId != null) {
if (index == null)
pageContext.removeAttribute(indexId, PageContext.PAGE_SCOPE);
else {
IteratedIndexExpression indexExpression = new IteratedIndexExpression(
index - begin);
VariableMapper vm = pageContext.getELContext()
.getVariableMapper();
if (vm != null) {
vm.setVariable(indexId, indexExpression);
}
}
}
}
/**
* Return expression for page variables
*
* @param expr
* @return
*/
private ValueExpression getVarExpression(ValueExpression expr) {
Object o = expr.getValue(pageContext.getELContext());
if (o.getClass().isArray() || o instanceof List) {
return new IndexedValueExpression(__value, index);
}
if (o instanceof Collection || o instanceof Iterator
|| o instanceof Enumeration || o instanceof Map
|| o instanceof String) {
if (iteratedExpression == null) {
iteratedExpression = new IteratedExpression(__value,
getDelims());
}
return new IteratedValueExpression(iteratedExpression, index);
}
throw new ELException("FOREACH_BAD_ITEMS: [" + o.getClass().getName() + "] is not iterable item. Only [List, Array, Collection, Enumeration, Map, String] are supported.");
}
/*
* Get the delimiter for string tokens. Used only for constructing the
* deferred expression for it.
*/
protected String getDelims() {
return ",";
}
/**
* @param end
* the end to set
*/
public void setEnd(ValueExpression end) {
this.__end = end;
}
/**
* @param begin
* the begin to set
*/
public void setBegin(ValueExpression begin) {
this.__begin = begin;
}
/**
* @param columns
* the columns to set
*/
public void setColumns(ValueExpression columns) {
this.__columns = columns;
}
/**
* @param index
* the index to set
*/
public void setIndex(ValueExpression index) {
this._index = index;
}
/**
* The current value for this component Setter for value
*
* @param value -
* new value
*/
public void setValue(ValueExpression __value) {
this.__value = __value;
}
/**
* A request-scope attribute via which the data object for the current row
* will be used when iterating Setter for var
*
* @param var -
* new value
*/
public void setVar(ValueExpression __var) {
this.__var = __var;
}
/**
* Corresponds to the HTML colspan attribute Setter for colspan
*
* @param colspan -
* new value
*/
public void setColspan(ValueExpression __colspan) {
this._colspan = __colspan;
}
/**
* Corresponds to the HTML class attribute Setter for styleClass
*
* @param styleClass -
* new value
*/
public void setStyleClass(ValueExpression __styleClass) {
this._styleClass = __styleClass;
}
/**
* Attribute defines width of column. Default value is "100px". Setter for
* width
*
* @param width -
* new value
*/
public void setWidth(ValueExpression __width) {
this._width = __width;
}
/**
* if "true" next column begins from the first row Setter for breakBefore
*
* @param breakBefore -
* new value
*/
public void setBreakBefore(ValueExpression __breakBefore) {
this._breakBefore = __breakBefore;
}
/**
*
* Setter for comparator
*
* @param comparator -
* new value
*/
public void setComparator(ValueExpression __comparator) {
this._comparator = __comparator;
}
/**
* Sets dir attr
*
* @param dir
*/
public void setDir(ValueExpression dir) {
_dir = dir;
}
/**
*
* Setter for filterBy
*
* @param filterBy -
* new value
*/
public void setFilterBy(ValueExpression __filterBy) {
this._filterBy = __filterBy;
}
/**
*
* Setter for filterDefaultLabel
*
* @param filterDefaultLabel -
* new value
*/
public void setFilterDefaultLabel(ValueExpression __filterDefaultLabel) {
this._filterDefaultLabel = __filterDefaultLabel;
}
/**
* Event for filter input that forces the filtration (default = onchange)
* Setter for filterEvent
*
* @param filterEvent -
* new value
*/
public void setFilterEvent(ValueExpression __filterEvent) {
this._filterEvent = __filterEvent;
}
/**
* Attribute defines a bean property which is used for filtering of a column
* Setter for filterExpression
*
* @param filterExpression -
* new value
*/
public void setFilterExpression(ValueExpression __filterExpression) {
this._filterExpression = __filterExpression;
}
/**
*
* Setter for filterMethod
*
* @param filterMethod -
* new value
*/
public void setFilterMethod(MethodExpression __filterMethod) {
this._filterMethod = __filterMethod;
}
/**
*
* Setter for filterValue
*
* @param filterValue -
* new value
*/
public void setFilterValue(ValueExpression __filterValue) {
this._filterValue = __filterValue;
}
/**
* Space-separated list of CSS style class(es) that are be applied to any
* footer generated for this table Setter for footerClass
*
* @param footerClass -
* new value
*/
public void setFooterClass(ValueExpression __footerClass) {
this._footerClass = __footerClass;
}
/**
* Space-separated list of CSS style class(es) that are be applied to any
* header generated for this table Setter for headerClass
*
* @param headerClass -
* new value
*/
public void setHeaderClass(ValueExpression __headerClass) {
this._headerClass = __headerClass;
}
/**
* Corresponds to the HTML rowspan attribute Setter for rowspan
*
* @param rowspan -
* new value
*/
public void setRowspan(ValueExpression __rowspan) {
this._rowspan = __rowspan;
}
/**
*
* Setter for selfSorted
*
* @param selfSorted -
* new value
*/
public void setSelfSorted(ValueExpression __selfSorted) {
this._selfSorted = __selfSorted;
}
/**
* Boolean attribute. If "true" it's possible to sort the column content
* after click on the header. Default value is "true" Setter for sortable
*
* @param sortable -
* new value
*/
public void setSortable(ValueExpression __sortable) {
this._sortable = __sortable;
}
/**
* Attribute defines a bean property which is used for sorting of a column
* Setter for sortExpression
*
* @param sortExpression -
* new value
*/
public void setSortExpression(ValueExpression __sortExpression) {
this._sortExpression = __sortExpression;
}
}