001: package org.andromda.cartridges.jsf.component.html;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.Collections;
006: import java.util.Iterator;
007: import java.util.List;
008: import java.util.ListIterator;
009:
010: import javax.faces.context.FacesContext;
011: import javax.faces.el.ValueBinding;
012:
013: import org.apache.commons.beanutils.PropertyUtils;
014: import org.apache.commons.lang.ObjectUtils;
015: import org.apache.commons.lang.StringUtils;
016: import org.apache.myfaces.component.html.ext.HtmlDataTable;
017:
018: /**
019: * Extends the datatable and provides the ability to have a backing value: this is
020: * useful when submitting tables of information.
021: *
022: * @author Chad Brandon
023: */
024: public class HtmlExtendedDataTable extends HtmlDataTable {
025: /**
026: * Overriden to provid population of the backingValue's items with the value's items (and
027: * updating the model afterwards).
028: *
029: * @see javax.faces.component.UIData#getValue()
030: */
031: public Object getValue() {
032: Object value = super .getValue();
033: Object backingValue = this .getBackingValue();
034: if (backingValue != null) {
035: final Class valueType = this .getValueType();
036: final Class backingValueType = this .getBackingValueType();
037: if (valueType != backingValueType) {
038: throw new IllegalArgumentException(
039: "The 'value' and 'backingValue' must be of the same type");
040: }
041: final String identifierColumnsString = this
042: .getIdentifierColumns();
043: final String[] identifierColumns = StringUtils
044: .isBlank(identifierColumnsString) ? new String[0]
045: : StringUtils.trimToEmpty(identifierColumnsString)
046: .split("\\s*,\\s*");
047:
048: // - handle collections
049: if (backingValue instanceof Collection) {
050: if (!(backingValue instanceof List)) {
051: backingValue = new ArrayList(
052: (Collection) backingValue);
053: }
054: if (!(value instanceof List)) {
055: value = new ArrayList((Collection) value);
056: }
057: final List backingValues = (List) backingValue;
058: final List values = value != null ? (List) value
059: : Collections.EMPTY_LIST;
060: for (final ListIterator iterator = backingValues
061: .listIterator(); iterator.hasNext();) {
062: final Object backingValueItem = iterator.next();
063: for (final Iterator valueIterator = values
064: .iterator(); valueIterator.hasNext();) {
065: final Object valueItem = valueIterator.next();
066: if (this .equal(backingValueItem, valueItem,
067: identifierColumns)) {
068: iterator.set(valueItem);
069: break;
070: }
071: }
072: }
073: value = backingValues;
074: }
075:
076: // - handle arrays
077: else if (backingValue.getClass().isArray()) {
078: final Object[] backingValues = (Object[]) backingValue;
079: final Object[] values = value != null ? (Object[]) value
080: : new Object[0];
081: for (int backingValueCtr = 0; backingValueCtr < backingValues.length; backingValueCtr++) {
082: final Object backingValueItem = backingValues[backingValueCtr];
083: for (int valueCtr = 0; valueCtr < values.length; valueCtr++) {
084: final Object valueItem = values[valueCtr];
085: if (this .equal(backingValueItem, valueItem,
086: identifierColumns)) {
087: backingValues[backingValueCtr] = valueItem;
088: break;
089: }
090: }
091: }
092: value = backingValues;
093: } else {
094: throw new IllegalArgumentException(
095: "The backing value must be a collection or array");
096: }
097: }
098: this .updateModelValue(value);
099: return value;
100: }
101:
102: /**
103: * Indicates whether or not this objects are equal, first by comparing them directly for
104: * equality and if not equal in that sense compares the columns returned by {@link #getIdentifierColumns()}.
105: * @param object1 the first object.
106: * @param object2 the second object.
107: * @param properties if equality fails, then these properties are compared for equality (all of them must
108: * be equal or else this operation will return false).
109: * @return true/false
110: */
111: private boolean equal(final Object object1, final Object object2,
112: final String[] properties) {
113: boolean equal = object1 == object2;
114: if (!equal && object1 != null && object2 != null) {
115: for (int ctr = 0; ctr < properties.length; ctr++) {
116: final String property = properties[ctr];
117: if (property != null && property.trim().length() > 0) {
118: final Object value1 = this .getProperty(object1,
119: property);
120: final Object value2 = this .getProperty(object2,
121: property);
122: if (value1 != null && value2 != null) {
123: equal = value1.equals(value2);
124: if (!equal) {
125: break;
126: }
127: }
128: }
129: }
130: }
131: return equal;
132: }
133:
134: /**
135: * Updates the model (i.e. underlying managed bean's value).
136: *
137: * @param value the value from which to update the model.
138: */
139: public void updateModelValue(final Object value) {
140: final ValueBinding binding = getValueBinding(VALUE);
141: if (binding != null) {
142: binding.setValue(this .getFacesContext(), value);
143: }
144: }
145:
146: /**
147: * The value attribute.
148: */
149: private static final String VALUE = "value";
150:
151: /**
152: * Gets the type of the backing value attribute.
153: * @return the backing value's type or null if the backing value
154: * isn't defined.
155: */
156: private Class getBackingValueType() {
157: return this .getBindingType(BACKING_VALUE);
158: }
159:
160: /**
161: * Gets the value's type attribute or null if value was not defined.
162: *
163: * @return the value's type or null if undefined.
164: */
165: private Class getValueType() {
166: return this .getBindingType(VALUE);
167: }
168:
169: /**
170: * Gets the binding type given the attribute name.
171: *
172: * @param name the name of the component's attribute.
173: * @return the binding type or null if the binding wasn't found.
174: */
175: private Class getBindingType(final String name) {
176: Class type = null;
177: ValueBinding binding = getValueBinding(name);
178: if (binding != null) {
179: final FacesContext context = this .getFacesContext();
180: type = binding.getType(context);
181: }
182: return type;
183: }
184:
185: private Object getProperty(final Object bean, final String property) {
186: try {
187: return PropertyUtils.getProperty(bean, property);
188: } catch (final Throwable throwable) {
189: throw new RuntimeException(throwable);
190: }
191: }
192:
193: private Object backingValue = null;
194:
195: /**
196: * The attribute that stores the backing value.
197: */
198: public static final String BACKING_VALUE = "backingValue";
199:
200: /**
201: * Retrieves the backing value of this extended data table (the backing value contains
202: * the values which the result of the value attribute are compared against).
203: * @return
204: */
205: protected Object getBackingValue() {
206: if (this .backingValue == null) {
207: final ValueBinding binding = this
208: .getValueBinding(BACKING_VALUE);
209: this .backingValue = binding == null ? null : binding
210: .getValue(getFacesContext());
211: }
212: return this .backingValue;
213: }
214:
215: /**
216: * Stores the identifier columns attribute.
217: */
218: private String identifierColumns;
219:
220: /**
221: * The attrribute that stores the identifier columns.
222: */
223: public static final String IDENTIFIER_COLUMNS = "identifierColumns";
224:
225: /**
226: * Retrieves the identifier columns component attribute.
227: *
228: * @return the identifier columns component attribute.
229: */
230: protected String getIdentifierColumns() {
231: if (this .identifierColumns == null) {
232: this .identifierColumns = (String) this .getAttributes().get(
233: IDENTIFIER_COLUMNS);
234: if (this .identifierColumns == null) {
235: final ValueBinding binding = this
236: .getValueBinding(IDENTIFIER_COLUMNS);
237: this.identifierColumns = binding == null ? null
238: : ObjectUtils.toString(binding
239: .getValue(getFacesContext()));
240: }
241: }
242: return this.identifierColumns;
243: }
244: }
|