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