package org.richfaces.component;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
import javax.faces.model.DataModel;
import org.ajax4jsf.Messages;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.SequenceDataModel;
import org.richfaces.component.util.MessageUtil;
import org.richfaces.model.OrderingListDataModel;
public abstract class UIOrderingList extends UIOrderingBaseComponent {
protected void processDecodes(FacesContext faces, Object argument) {
if (!this.isRendered())
return;
this.decode(faces);
SubmittedValue submittedValue = UIOrderingList.this.submittedValueHolder;
if (submittedValue != null) {
Object modelValue = getValue();
Iterator iterator = submittedValue.dataMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
Object value = entry.getValue();
if (!isSuitableValue(modelValue, value)) {
String messageText = Messages.getMessage(
Messages.INVALID_VALUE, MessageUtil.getLabel(faces, this), value);
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, messageText, null);
faces.addMessage(this.getClientId(faces), message);
setValid(false);
break;
}
}
}
this.iterate(faces, decodeVisitor, argument);
if (isImmediate()) {
executeValidate(faces);
}
if (!isValid()) {
faces.renderResponse();
}
}
private ValueHolder valueHolder;
protected static final class SubmittedValue implements Serializable {
/**
*
*/
private static final long serialVersionUID = 5860506816451180551L;
private Map dataMap;
private Set selection;
private Object activeItem;
private boolean _null = false;
public SubmittedValue(Map dataMap, Set selection, Object activeItem) {
this.dataMap = dataMap;
this.selection = selection;
this.activeItem = activeItem;
}
public void setNull() {
_null = true;
}
public boolean isNull() {
return _null;
}
public void resetDataModel() {
if (_null) {
this.dataMap = null;
}
}
}
private final class ModelItemState implements ItemState {
private Collection selectedItems;
private Object activeItem;
public ModelItemState(Collection selectedItems, Object activeItem) {
super();
this.selectedItems = selectedItems;
this.activeItem = activeItem;
}
public boolean isSelected() {
return selectedItems != null && selectedItems.contains(getRowData());
}
public boolean isActive() {
return activeItem != null && activeItem.equals(getRowData());
}
}
protected ExtendedDataModel createDataModel() {
Map modelMap = null;
if (submittedValueHolder != null) {
modelMap = submittedValueHolder.dataMap;
}
if (modelMap != null) {
OrderingListDataModel dataModel = new OrderingListDataModel();
dataModel.setWrappedData(modelMap);
return dataModel;
} else {
DataModel dataModel = createDataModel(getValue());
return new SequenceDataModel(dataModel);
}
}
private transient SubmittedValue submittedValueHolder = null;
public final static class ValueHolder implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4216115242421460529L;
private Object value;
private Set selection;
private boolean selectionSet;
private Object activeItem;
private boolean activeItemSet;
public boolean isTransient() {
//TODO null collection == [] ?
return value == null && (selection == null || selection.isEmpty()) && !selectionSet &&
activeItem == null && !activeItemSet;
}
public void restoreState(FacesContext context, UIOrderingList list, Object _state) {
Object[] state = (Object[]) _state;
value = restoreAttachedState(context, state[0]);
selection = (Set) restoreAttachedState(context, state[1]);
selectionSet = Boolean.TRUE.equals(state[2]);
activeItem = restoreAttachedState(context, state[3]);
activeItemSet = Boolean.TRUE.equals(state[4]);
}
public Object saveState(FacesContext context, final UIOrderingList list) {
// Object rowKey = list.getRowKey();
//
// final HashSet selectionKeySet = new HashSet();
// final Object[] activeItemSet = new Object[1];
// try {
// list.walk(context, new DataVisitor() {
//
// public void process(FacesContext context, Object rowKey,
// Object argument) throws IOException {
//
// list.setRowKey(context, rowKey);
// Object data = list.getRowData();
//
// if (data != null) {
// if (data.equals(activeItem)) {
// activeItemSet[0] = rowKey;
// }
//
// if (selection != null && selection.contains(data)) {
// selectionKeySet.add(rowKey);
// }
// }
// }
//
// }, null);
// } catch (IOException e) {
// throw new FacesException(e.getLocalizedMessage(), e);
// }
Object[] state = new Object[5];
state[0] = saveAttachedState(context, value);
state[1] = saveAttachedState(context, selection);
state[2] = this.selectionSet ? Boolean.TRUE : Boolean.FALSE;
state[3] = saveAttachedState(context, activeItem);
state[4] = this.activeItemSet ? Boolean.TRUE : Boolean.FALSE;
return state;
}
public void setTransient(boolean newTransientValue) {
if (newTransientValue) {
throw new IllegalArgumentException();
}
}
}
public void addValueChangeListener(ValueChangeListener listener) {
addFacesListener(listener);
}
public abstract MethodBinding getValueChangeListener();
public ValueChangeListener[] getValueChangeListeners() {
return (ValueChangeListener[]) getFacesListeners(ValueChangeListener.class);
}
public abstract boolean isImmediate();
public abstract boolean isRequired();
public abstract boolean isValid();
public void removeValueChangeListener(ValueChangeListener listener) {
removeFacesListener(listener);
}
public void setSubmittedString(Map submittedString, Set selection, Object activeItem) {
this.submittedValueHolder = new SubmittedValue(submittedString, selection, activeItem);
}
public Object getSubmittedValue() {
return submittedValueHolder;
}
public void setSubmittedValue(Object object) {
this.submittedValueHolder = (SubmittedValue) object;
}
protected Object saveIterationState() {
return valueHolder;
}
protected void restoreIterationState(Object object) {
this.valueHolder = (ValueHolder) object;
}
public abstract void setImmediate(boolean immediate);
public abstract void setRequired(boolean required);
public abstract void setValid(boolean valid);
public abstract void setValueChangeListener(MethodBinding valueChangeMethod);
public abstract Converter getConverter();
public abstract void setConverter(Converter converter);
/**
* <p>Specialized decode behavior on top of that provided by the
* superclass. In addition to the standard
* <code>processDecodes</code> behavior inherited from {@link
* UIComponentBase}, calls <code>validate()</code> if the the
* <code>immediate</code> property is true; if the component is
* invalid afterwards or a <code>RuntimeException</code> is thrown,
* calls {@link FacesContext#renderResponse}. </p>
* @exception NullPointerException {@inheritDoc}
*/
public void processDecodes(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
super.processDecodes(context);
}
/**
* <p>In addition to the standard <code>processValidators</code> behavior
* inherited from {@link UIComponentBase}, calls <code>validate()</code>
* if the <code>immediate</code> property is false (which is the
* default); if the component is invalid afterwards, calls
* {@link FacesContext#renderResponse}.
* If a <code>RuntimeException</code> is thrown during
* validation processing, calls {@link FacesContext#renderResponse}
* and re-throw the exception.
* </p>
* @exception NullPointerException {@inheritDoc}
*/
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
super.processValidators(context);
}
/**
* <p>In addition to the standard <code>processUpdates</code> behavior
* inherited from {@link UIComponentBase}, calls
* <code>updateModel()</code>.
* If the component is invalid afterwards, calls
* {@link FacesContext#renderResponse}.
* If a <code>RuntimeException</code> is thrown during
* update processing, calls {@link FacesContext#renderResponse}
* and re-throw the exception.
* </p>
* @exception NullPointerException {@inheritDoc}
*/
public void processUpdates(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
super.processUpdates(context);
}
/**
* <p>In addition to to the default {@link UIComponent#broadcast}
* processing, pass the {@link ValueChangeEvent} being broadcast to the
* method referenced by <code>valueChangeListener</code> (if any).</p>
*
* @param event {@link FacesEvent} to be broadcast
*
* @exception AbortProcessingException Signal the JavaServer Faces
* implementation that no further processing on the current event
* should be performed
* @exception IllegalArgumentException if the implementation class
* of this {@link FacesEvent} is not supported by this component
* @exception NullPointerException if <code>event</code> is
* <code>null</code>
*/
public void broadcast(FacesEvent event)
throws AbortProcessingException {
// Perform standard superclass processing
super.broadcast(event);
if (event instanceof ValueChangeEvent) {
MethodBinding method = getValueChangeListener();
if (method != null) {
FacesContext context = getFacesContext();
method.invoke(context, new Object[] { event });
}
}
}
protected final UpdateModelCommand updateActiveItemCommand = new UpdateModelCommand() {
public void execute(FacesContext context) {
if (valueHolder.activeItemSet) {
ValueBinding vb = getValueBinding("activeItem");
if (vb != null) {
vb.setValue(context, valueHolder.activeItem);
valueHolder.activeItem = null;
valueHolder.activeItemSet = false;
}
}
}
};
protected final UpdateModelCommand updateSelectionCommand = new UpdateModelCommand() {
public void execute(FacesContext context) {
if (valueHolder.selectionSet) {
ValueBinding vb = getValueBinding("selection");
if (vb != null) {
vb.setValue(context, valueHolder.selection);
valueHolder.selection = null;
valueHolder.selectionSet = false;
}
}
}
};
protected final UpdateModelCommand updateValueCommand = new UpdateModelCommand() {
public void execute(FacesContext context) {
if (isLocalValueSet() && valueHolder != null) {
ValueBinding vb = getValueBinding("value");
if (vb != null) {
vb.setValue(context, valueHolder.value);
setValue(null);
setLocalValueSet(false);
}
}
}
};
/**
* <p>Perform the following algorithm to update the model dataMap
* associated with this {@link UIInput}, if any, as appropriate.</p>
* <ul>
* <li>If the <code>valid</code> property of this component is
* <code>false</code>, take no further action.</li>
* <li>If the <code>localValueSet</code> property of this component is
* <code>false</code>, take no further action.</li>
* <li>If no {@link ValueBinding} for <code>value</code> exists,
* take no further action.</li>
* <li>Call <code>setValue()</code> method of the {@link ValueBinding}
* to update the value that the {@link ValueBinding} points at.</li>
* <li>If the <code>setValue()</code> method returns successfully:
* <ul>
* <li>Clear the local value of this {@link UIInput}.</li>
* <li>Set the <code>localValueSet</code> property of this
* {@link UIInput} to false.</li>
* </ul></li>
* <li>If the <code>setValue()</code> method call fails:
* <ul>
* <li>Enqueue an error message by calling <code>addMessage()</code>
* on the specified {@link FacesContext} instance.</li>
* <li>Set the <code>valid</code> property of this {@link UIInput}
* to <code>false</code>.</li>
* </ul></li>
* </ul>
*
* @param context {@link FacesContext} for the request we are processing
*
* @exception NullPointerException if <code>context</code>
* is <code>null</code>
*/
public void updateModel(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isValid()) {
return;
}
if (valueHolder != null) {
updateModel(context, updateValueCommand);
updateModel(context, updateSelectionCommand);
updateModel(context, updateActiveItemCommand);
}
}
// ------------------------------------------------------ Validation Methods
/**
* <p>Perform the following algorithm to validate the local value of
* this {@link UIInput}.</p>
* <ul>
* <li>Retrieve the submitted value with <code>getSubmittedValue()</code>.
* If this returns null, exit without further processing. (This
* indicates that no value was submitted for this component.)</li>
*
* <li> Convert the submitted value into a "local value" of the
* appropriate dataMap type by calling {@link #getConvertedValue}.</li>
*
* <li>Validate the property by calling {@link #validateValue}.</li>
*
* <li>If the <code>valid</code> property of this component is still
* <code>true</code>, retrieve the previous value of the component
* (with <code>getValue()</code>), store the new local value using
* <code>setValue()</code>, and reset the submitted value to
* null. If the local value is different from
* the previous value of this component, fire a
* {@link ValueChangeEvent} to be broadcast to all interested
* listeners.</li>
* </ul>
*
* <p>Application components implementing {@link UIInput} that wish to
* perform validation with logic embedded in the component should perform
* their own correctness checks, and then call the
* <code>super.validate()</code> method to perform the standard
* processing described above.</p>
*
* @param context The {@link FacesContext} for the current request
*
* @exception NullPointerException if <code>context</code>
* is null
*/
public void validate(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Submitted value == null means "the component was not submitted
// at all"; validation should not continue
// http://jira.jboss.com/jira/browse/RF-3852
// if (submittedValueHolder == null) {
// return;
// }
Object previousValue = getValue();
Object newValue = null;
try {
if (previousValue == null) {
previousValue = Collections.EMPTY_LIST;
}
try {
final ArrayList list = new ArrayList(getRowCount());
Object key = getRowKey();
captureOrigValue(context);
walk(context, new DataVisitor() {
public void process(FacesContext context, Object rowKey,
Object argument) throws IOException {
setRowKey(context, rowKey);
list.add(getRowData());
}
}, null);
setRowKey(key);
restoreOrigValue(context);
newValue = createContainer(list, previousValue);
} catch (IOException e) {
throw new ConverterException(e.getLocalizedMessage(), e);
}
}
catch (ConverterException ce) {
Object submittedValue = submittedValueHolder;
addConversionErrorMessage(context, ce, submittedValue);
setValid(false);
}
validateValue(context, newValue);
// If our value is valid, store the new value, erase the
// "submitted" value, and emit a ValueChangeEvent if appropriate
if (isValid()) {
setSelection(submittedValueHolder.selection);
setActiveItem(submittedValueHolder.activeItem);
setValue(newValue);
if (compareValues(previousValue, newValue)) {
queueEvent(new ValueChangeEvent(this, previousValue, newValue));
}
this.submittedValueHolder.setNull();
}
}
protected void resetDataModel() {
super.resetDataModel();
if (this.submittedValueHolder != null) {
this.submittedValueHolder.resetDataModel();
}
}
public ItemState getItemState() {
if (submittedValueHolder != null && !submittedValueHolder.isNull()) {
return new ModelItemState(submittedValueHolder.selection,
submittedValueHolder.activeItem);
} else {
return new ModelItemState(getSelection(), getActiveItem());
}
}
public abstract String getControlsType();
public abstract void setControlsType(String type);
public Set getSelection() {
if (valueHolder != null && valueHolder.selection != null) {
return valueHolder.selection;
} else {
ValueBinding vb = getValueBinding("selection");
if (vb != null) {
return (Set) vb.getValue(FacesContext.getCurrentInstance());
}
}
return null;
}
public void setSelection(Set selection) {
createValueHolder();
valueHolder.selection = selection;
valueHolder.selectionSet = true;
}
public Object getActiveItem() {
if (valueHolder != null && valueHolder.activeItem != null) {
return valueHolder.activeItem;
} else {
ValueBinding vb = getValueBinding("activeItem");
if (vb != null) {
return vb.getValue(FacesContext.getCurrentInstance());
}
}
return null;
}
public void setActiveItem(Object activeItem) {
createValueHolder();
valueHolder.activeItem = activeItem;
valueHolder.activeItemSet = true;
}
private void createValueHolder() {
if (valueHolder == null) {
valueHolder = new ValueHolder();
}
}
public void setValue(Object value) {
if (value instanceof ValueHolder) {
this.valueHolder = (ValueHolder) value;
} else {
createValueHolder();
valueHolder.value = value;
setLocalValueSet(true);
}
}
public Object getLocalValue() {
return valueHolder;
}
public Object getValue() {
if (valueHolder != null && valueHolder.value != null) {
return valueHolder.value;
}
ValueBinding ve = getValueBinding("value");
if (ve != null) {
return (ve.getValue(getFacesContext()));
} else {
return (null);
}
}
public Object saveState(FacesContext faces) {
Object[] state = new Object[2];
state[0] = super.saveState(faces);
if (this.valueHolder != null) {
state[1] = this.valueHolder.saveState(faces, this);
}
return state;
}
public void restoreState(FacesContext faces, Object object) {
Object[] state = (Object[]) object;
super.restoreState(faces, state[0]);
if (state[1] != null) {
this.valueHolder = new ValueHolder();
this.valueHolder.restoreState(faces, this, state[1]);
}
}
}