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.lang.StringUtils; 021 import org.slf4j.Logger; 022 import org.slf4j.LoggerFactory; 023 import org.apache.myfaces.tobago.compat.FacesUtils; 024 import org.apache.myfaces.tobago.event.SortActionEvent; 025 import org.apache.myfaces.tobago.internal.component.AbstractUISheet; 026 import org.apache.myfaces.tobago.model.SheetState; 027 import org.apache.myfaces.tobago.util.BeanComparator; 028 029 import javax.faces.component.UIColumn; 030 import javax.faces.component.UICommand; 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.component.UISelectMany; 036 import javax.faces.component.UISelectOne; 037 import javax.faces.context.FacesContext; 038 import javax.faces.model.DataModel; 039 import java.util.ArrayList; 040 import java.util.Arrays; 041 import java.util.Collections; 042 import java.util.Comparator; 043 import java.util.Iterator; 044 import java.util.List; 045 046 /* 047 * User: weber 048 * Date: Mar 7, 2005 049 * Time: 4:01:27 PM 050 */ 051 public class Sorter { 052 053 private static final Logger LOG = LoggerFactory.getLogger(Sorter.class); 054 055 private Comparator comparator; 056 057 public void perform(SortActionEvent sortEvent) { 058 if (LOG.isDebugEnabled()) { 059 LOG.debug("sorterId = {}", sortEvent.getComponent().getId()); 060 } 061 UIColumn column = sortEvent.getColumn(); 062 AbstractUISheet data = (AbstractUISheet) sortEvent.getComponent(); 063 064 Object value = data.getValue(); 065 if (value instanceof DataModel) { 066 value = ((DataModel) value).getWrappedData(); 067 } 068 FacesContext facesContext = FacesContext.getCurrentInstance(); 069 SheetState sheetState = data.getSheetState(facesContext); 070 071 Comparator actualComparator = null; 072 073 if (value instanceof List || value instanceof Object[]) { 074 String sortProperty; 075 076 try { 077 078 UIComponent child = getFirstSortableChild(column.getChildren()); 079 if (child != null) { 080 String var = data.getVar(); 081 082 if (FacesUtils.hasValueBindingOrValueExpression(child, "value")) { 083 String expressionString = FacesUtils.getExpressionString(child, "value"); 084 if (isSimpleProperty(expressionString)) { 085 if (expressionString.startsWith("#{") 086 && expressionString.endsWith("}")) { 087 expressionString = 088 expressionString.substring(2, 089 expressionString.length() - 1); 090 } 091 sortProperty = expressionString.substring(var.length() + 1); 092 093 actualComparator = new BeanComparator( 094 sortProperty, comparator, !sheetState.isAscending()); 095 096 if (LOG.isDebugEnabled()) { 097 LOG.debug("Sort property is {}", sortProperty); 098 } 099 } else { 100 101 boolean descending = !sheetState.isAscending(); 102 actualComparator = 103 FacesUtils.getBindingOrExpressionComparator(facesContext, child, var, descending, comparator); 104 } 105 } 106 107 } else { 108 LOG.error("No sorting performed. Value is not instanceof List or Object[]!"); 109 unsetSortableAttribute(column); 110 return; 111 } 112 } catch (Exception e) { 113 LOG.error("Error while extracting sortMethod :" + e.getMessage(), e); 114 if (column != null) { 115 unsetSortableAttribute(column); 116 } 117 return; 118 } 119 120 // TODO: locale / comparator parameter? 121 // don't compare numbers with Collator.getInstance() comparator 122 // Comparator comparator = Collator.getInstance(); 123 // comparator = new RowComparator(ascending, method); 124 125 // memorize selected rows 126 List<Object> selectedDataRows = null; 127 if (sheetState.getSelectedRows() != null && sheetState.getSelectedRows().size() > 0) { 128 selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size()); 129 Object dataRow; 130 for (Integer index : sheetState.getSelectedRows()) { 131 if (value instanceof List) { 132 dataRow = ((List) value).get(index); 133 } else { 134 dataRow = ((Object[]) value)[index]; 135 } 136 selectedDataRows.add(dataRow); 137 } 138 } 139 140 // do sorting 141 if (value instanceof List) { 142 Collections.sort((List) value, actualComparator); 143 } else { // value is instanceof Object[] 144 Arrays.sort((Object[]) value, actualComparator); 145 } 146 147 // restore selected rows 148 if (selectedDataRows != null) { 149 sheetState.getSelectedRows().clear(); 150 for (Object dataRow : selectedDataRows) { 151 int index = -1; 152 if (value instanceof List) { 153 for (int i = 0; i < ((List) value).size() && index < 0; i++) { 154 if (dataRow == ((List) value).get(i)) { 155 index = i; 156 } 157 } 158 } else { 159 for (int i = 0; i < ((Object[]) value).length && index < 0; i++) { 160 if (dataRow == ((Object[]) value)[i]) { 161 index = i; 162 } 163 } 164 } 165 if (index >= 0) { 166 sheetState.getSelectedRows().add(index); 167 } 168 } 169 } 170 171 } else { // DataModel?, ResultSet, Result or Object 172 LOG.warn("Sorting not supported for type " 173 + (value != null ? value.getClass().toString() : "null")); 174 } 175 } 176 177 // XXX needs to be tested 178 // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w 179 boolean isSimpleProperty(String expressionString) { 180 if (expressionString.startsWith("#{") && expressionString.endsWith("}")) { 181 String inner = expressionString.substring(2, expressionString.length() - 1); 182 String[] parts = StringUtils.split(inner, "."); 183 for (String part : parts) { 184 if (!StringUtils.isAlpha(part)) { 185 return false; 186 } 187 } 188 return true; 189 } 190 return false; 191 } 192 193 private void unsetSortableAttribute(UIColumn uiColumn) { 194 LOG.warn("removing attribute sortable from column " + uiColumn.getId()); 195 uiColumn.getAttributes().put(Attributes.SORTABLE, Boolean.FALSE); 196 } 197 198 private UIComponent getFirstSortableChild(List children) { 199 UIComponent child = null; 200 201 for (Iterator iter = children.iterator(); iter.hasNext();) { 202 child = (UIComponent) iter.next(); 203 if (child instanceof UICommand 204 || child instanceof javax.faces.component.UIPanel) { 205 child = getFirstSortableChild(child.getChildren()); 206 } 207 if (child instanceof UISelectMany 208 || child instanceof UISelectOne 209 || child instanceof UISelectBoolean) { 210 continue; 211 } else if (child instanceof UIInput 212 && RendererTypes.HIDDEN.equals(child.getRendererType())) { 213 continue; 214 } else if (child instanceof UIOutput) { 215 break; 216 } 217 } 218 return child; 219 } 220 221 public Comparator getComparator() { 222 return comparator; 223 } 224 225 public void setComparator(Comparator comparator) { 226 this.comparator = comparator; 227 } 228 } 229