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