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 String attributeName = child instanceof UICommand ? TobagoConstants.ATTR_LABEL:TobagoConstants.ATTR_VALUE; 086 ValueBinding valueBinding = child.getValueBinding(attributeName); 087 088 089 if (valueBinding != null) { 090 String var = data.getVar(); 091 if (isSimpleProperty(valueBinding.getExpressionString())) { 092 String expressionString = valueBinding.getExpressionString(); 093 if (expressionString.startsWith("#{") 094 && expressionString.endsWith("}")) { 095 expressionString = 096 expressionString.substring(2, 097 expressionString.length() - 1); 098 } 099 sortProperty = expressionString.substring(var.length() + 1); 100 101 actualComparator = new BeanComparator( 102 sortProperty, comparator, !sheetState.isAscending()); 103 104 if (LOG.isDebugEnabled()) { 105 LOG.debug("Sort property is " + sortProperty); 106 } 107 } else { 108 actualComparator = new ValueBindingComparator(facesContext, var, 109 valueBinding, !sheetState.isAscending(), comparator); 110 } 111 } 112 113 } else { 114 LOG.error("No sorting performed. Value is not instanceof List or Object[]!"); 115 unsetSortableAttribute(column); 116 return null; 117 } 118 } catch (Exception e) { 119 LOG.error("Error while extracting sortMethod :" + e.getMessage(), e); 120 if (column != null) { 121 unsetSortableAttribute(column); 122 } 123 return null; 124 } 125 126 // TODO: locale / comparator parameter? 127 // don't compare numbers with Collator.getInstance() comparator 128 // Comparator comparator = Collator.getInstance(); 129 // comparator = new RowComparator(ascending, method); 130 131 // memorize selected rows 132 List<Object> selectedDataRows = null; 133 if (sheetState.getSelectedRows() != null && sheetState.getSelectedRows().size() > 0) { 134 selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size()); 135 Object dataRow; 136 for (Integer index : sheetState.getSelectedRows()) { 137 if (value instanceof List) { 138 dataRow = ((List) value).get(index); 139 } else { 140 dataRow = ((Object[]) value)[index]; 141 } 142 selectedDataRows.add(dataRow); 143 } 144 } 145 146 // do sorting 147 if (value instanceof List) { 148 Collections.sort((List) value, actualComparator); 149 } else { // value is instanceof Object[] 150 Arrays.sort((Object[]) value, actualComparator); 151 } 152 153 // restore selected rows 154 if (selectedDataRows != null) { 155 sheetState.getSelectedRows().clear(); 156 for (Object dataRow : selectedDataRows) { 157 int index = -1; 158 if (value instanceof List) { 159 for (int i = 0; i < ((List) value).size() && index < 0; i++) { 160 if (dataRow == ((List) value).get(i)) { 161 index = i; 162 } 163 } 164 } else { 165 for (int i = 0; i < ((Object[]) value).length && index < 0; i++) { 166 if (dataRow == ((Object[]) value)[i]) { 167 index = i; 168 } 169 } 170 } 171 if (index >= 0) { 172 sheetState.getSelectedRows().add(index); 173 } 174 } 175 } 176 177 } else { // DataModel?, ResultSet, Result or Object 178 LOG.warn("Sorting not supported for type " 179 + (value != null ? value.getClass().toString() : "null")); 180 } 181 } 182 return null; 183 } 184 185 // XXX needs to be tested 186 // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w 187 boolean isSimpleProperty(String expressionString) { 188 if (expressionString.startsWith("#{") && expressionString.endsWith("}")) { 189 String inner = expressionString.substring(2, expressionString.length() - 1); 190 String[] parts = StringUtils.split(inner, "."); 191 for (String part : parts) { 192 if (!StringUtils.isAlpha(part)) { 193 return false; 194 } 195 } 196 return true; 197 } 198 return false; 199 } 200 201 private void unsetSortableAttribute(UIColumn uiColumn) { 202 LOG.warn("removing attribute sortable from column " + uiColumn.getId()); 203 uiColumn.getAttributes().put(ATTR_SORTABLE, Boolean.FALSE); 204 } 205 206 private UIComponent getFirstSortableChild(List children) { 207 UIComponent child = null; 208 209 for (Iterator iter = children.iterator(); iter.hasNext();) { 210 child = (UIComponent) iter.next(); 211 if (child instanceof UISelectMany 212 || child instanceof UISelectOne 213 || child instanceof UISelectBoolean 214 || child instanceof UICommand 215 || (child instanceof UIInput && TobagoConstants.RENDERER_TYPE_HIDDEN.equals(child.getRendererType()))) { 216 continue; 217 // look for a better component if any 218 } 219 if (child instanceof UIOutput) { 220 break; 221 } 222 if (child instanceof javax.faces.component.UICommand 223 || child instanceof javax.faces.component.UIPanel) { 224 child = getFirstSortableChild(child.getChildren()); 225 if (child instanceof UIOutput) { 226 break; 227 } 228 } 229 } 230 return child; 231 } 232 233 public Class getType(FacesContext facescontext) 234 throws MethodNotFoundException { 235 return String.class; 236 } 237 238 public Comparator getComparator() { 239 return comparator; 240 } 241 242 public void setComparator(Comparator comparator) { 243 this.comparator = comparator; 244 } 245 } 246