001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.myfaces.tobago.component;
021
022 import org.apache.commons.logging.Log;
023 import org.apache.commons.logging.LogFactory;
024 import org.apache.commons.lang.StringUtils;
025 import org.apache.myfaces.tobago.TobagoConstants;
026 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SORTABLE;
027 import org.apache.myfaces.tobago.event.SortActionEvent;
028 import org.apache.myfaces.tobago.model.SheetState;
029 import org.apache.myfaces.tobago.util.BeanComparator;
030 import org.apache.myfaces.tobago.util.ValueBindingComparator;
031
032 import javax.faces.component.UIColumn;
033 import javax.faces.component.UIComponent;
034 import javax.faces.component.UIInput;
035 import javax.faces.component.UIOutput;
036 import javax.faces.component.UISelectBoolean;
037 import javax.faces.context.FacesContext;
038 import javax.faces.el.EvaluationException;
039 import javax.faces.el.MethodBinding;
040 import javax.faces.el.MethodNotFoundException;
041 import javax.faces.el.ValueBinding;
042 import javax.faces.model.DataModel;
043
044 import java.util.ArrayList;
045 import java.util.Arrays;
046 import java.util.Collections;
047 import java.util.Comparator;
048 import java.util.Iterator;
049 import java.util.List;
050
051 /*
052 * User: weber
053 * Date: Mar 7, 2005
054 * Time: 4:01:27 PM
055 */
056 public class Sorter extends MethodBinding {
057
058 private static final Log LOG = LogFactory.getLog(Sorter.class);
059
060 private Comparator comparator;
061
062 public Object invoke(FacesContext facesContext, Object[] aobj)
063 throws EvaluationException {
064 if (aobj[0] instanceof SortActionEvent) {
065 SortActionEvent sortEvent = (SortActionEvent) aobj[0];
066 if (LOG.isDebugEnabled()) {
067 LOG.debug("sorterId = " + sortEvent.getComponent().getId());
068 }
069 UIColumn column = sortEvent.getColumn();
070 UIData data = sortEvent.getSheet();
071
072 Object value = data.getValue();
073 if (value instanceof DataModel) {
074 value = ((DataModel) value).getWrappedData();
075 }
076 SheetState sheetState = data.getSheetState(facesContext);
077
078 Comparator actualComparator = null;
079
080 if (value instanceof List || value instanceof Object[]) {
081 String sortProperty;
082
083 try {
084
085 UIComponent child = getFirstSortableChild(column.getChildren());
086 if (child != null) {
087 String attributeName = child instanceof UICommand ? TobagoConstants.ATTR_LABEL:TobagoConstants.ATTR_VALUE;
088 ValueBinding valueBinding = child.getValueBinding(attributeName);
089
090
091 if (valueBinding != null) {
092 String var = data.getVar();
093 if (isSimpleProperty(valueBinding.getExpressionString())) {
094 String expressionString = valueBinding.getExpressionString();
095 if (expressionString.startsWith("#{")
096 && expressionString.endsWith("}")) {
097 expressionString =
098 expressionString.substring(2,
099 expressionString.length() - 1);
100 }
101 sortProperty = expressionString.substring(var.length() + 1);
102
103 actualComparator = new BeanComparator(
104 sortProperty, comparator, !sheetState.isAscending());
105
106 if (LOG.isDebugEnabled()) {
107 LOG.debug("Sort property is " + sortProperty);
108 }
109 } else {
110 actualComparator = new ValueBindingComparator(facesContext, var,
111 valueBinding, !sheetState.isAscending(), comparator);
112 }
113 }
114
115 } else {
116 LOG.error("No sorting performed. Value is not instanceof List or Object[]!");
117 unsetSortableAttribute(column);
118 return null;
119 }
120 } catch (Exception e) {
121 LOG.error("Error while extracting sortMethod :" + e.getMessage(), e);
122 if (column != null) {
123 unsetSortableAttribute(column);
124 }
125 return null;
126 }
127
128 // TODO: locale / comparator parameter?
129 // don't compare numbers with Collator.getInstance() comparator
130 // Comparator comparator = Collator.getInstance();
131 // comparator = new RowComparator(ascending, method);
132
133 // memorize selected rows
134 List<Object> selectedDataRows = null;
135 if (sheetState.getSelectedRows() != null && sheetState.getSelectedRows().size() > 0) {
136 selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size());
137 Object dataRow;
138 for (Integer index : sheetState.getSelectedRows()) {
139 if (value instanceof List) {
140 dataRow = ((List) value).get(index);
141 } else {
142 dataRow = ((Object[]) value)[index];
143 }
144 selectedDataRows.add(dataRow);
145 }
146 }
147
148 // do sorting
149 if (value instanceof List) {
150 Collections.sort((List) value, actualComparator);
151 } else { // value is instanceof Object[]
152 Arrays.sort((Object[]) value, actualComparator);
153 }
154
155 // restore selected rows
156 if (selectedDataRows != null) {
157 sheetState.getSelectedRows().clear();
158 for (Object dataRow : selectedDataRows) {
159 int index = -1;
160 if (value instanceof List) {
161 for (int i = 0; i < ((List) value).size() && index < 0; i++) {
162 if (dataRow == ((List) value).get(i)) {
163 index = i;
164 }
165 }
166 } else {
167 for (int i = 0; i < ((Object[]) value).length && index < 0; i++) {
168 if (dataRow == ((Object[]) value)[i]) {
169 index = i;
170 }
171 }
172 }
173 if (index >= 0) {
174 sheetState.getSelectedRows().add(index);
175 }
176 }
177 }
178
179 } else { // DataModel?, ResultSet, Result or Object
180 LOG.warn("Sorting not supported for type "
181 + (value != null ? value.getClass().toString() : "null"));
182 }
183 }
184 return null;
185 }
186
187 // XXX needs to be tested
188 // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w
189 boolean isSimpleProperty(String expressionString) {
190 if (expressionString.startsWith("#{") && expressionString.endsWith("}")) {
191 String inner = expressionString.substring(2, expressionString.length() - 1);
192 String[] parts = StringUtils.split(inner, ".");
193 for (String part : parts) {
194 if (!StringUtils.isAlpha(part)) {
195 return false;
196 }
197 }
198 return true;
199 }
200 return false;
201 }
202
203 private void unsetSortableAttribute(UIColumn uiColumn) {
204 LOG.warn("removing attribute sortable from column " + uiColumn.getId());
205 uiColumn.getAttributes().put(ATTR_SORTABLE, Boolean.FALSE);
206 }
207
208 private UIComponent getFirstSortableChild(List children) {
209 UIComponent child = null;
210
211 for (Iterator iter = children.iterator(); iter.hasNext();) {
212 child = (UIComponent) iter.next();
213 if (child instanceof UISelectMany
214 || child instanceof UISelectOne
215 || child instanceof UISelectBoolean
216 || (child instanceof UICommand && child.getChildren().isEmpty())
217 || (child instanceof UIInput && TobagoConstants.RENDERER_TYPE_HIDDEN.equals(child.getRendererType()))) {
218 continue;
219 // look for a better component if any
220 }
221 if (child instanceof UIOutput) {
222 break;
223 }
224 if (child instanceof javax.faces.component.UICommand
225 || child instanceof javax.faces.component.UIPanel) {
226 child = getFirstSortableChild(child.getChildren());
227 if (child instanceof UIOutput) {
228 break;
229 }
230 }
231 }
232 return child;
233 }
234
235 public Class getType(FacesContext facescontext)
236 throws MethodNotFoundException {
237 return String.class;
238 }
239
240 public Comparator getComparator() {
241 return comparator;
242 }
243
244 public void setComparator(Comparator comparator) {
245 this.comparator = comparator;
246 }
247 }
248