001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans.support; 018 019import java.util.Arrays; 020import java.util.Comparator; 021import java.util.List; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025 026import org.springframework.beans.BeanWrapperImpl; 027import org.springframework.beans.BeansException; 028import org.springframework.lang.Nullable; 029import org.springframework.util.StringUtils; 030 031/** 032 * PropertyComparator performs a comparison of two beans, 033 * evaluating the specified bean property via a BeanWrapper. 034 * 035 * @author Juergen Hoeller 036 * @author Jean-Pierre Pawlak 037 * @since 19.05.2003 038 * @param <T> the type of objects that may be compared by this comparator 039 * @see org.springframework.beans.BeanWrapper 040 */ 041public class PropertyComparator<T> implements Comparator<T> { 042 043 protected final Log logger = LogFactory.getLog(getClass()); 044 045 private final SortDefinition sortDefinition; 046 047 private final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(false); 048 049 050 /** 051 * Create a new PropertyComparator for the given SortDefinition. 052 * @see MutableSortDefinition 053 */ 054 public PropertyComparator(SortDefinition sortDefinition) { 055 this.sortDefinition = sortDefinition; 056 } 057 058 /** 059 * Create a PropertyComparator for the given settings. 060 * @param property the property to compare 061 * @param ignoreCase whether upper and lower case in String values should be ignored 062 * @param ascending whether to sort ascending (true) or descending (false) 063 */ 064 public PropertyComparator(String property, boolean ignoreCase, boolean ascending) { 065 this.sortDefinition = new MutableSortDefinition(property, ignoreCase, ascending); 066 } 067 068 /** 069 * Return the SortDefinition that this comparator uses. 070 */ 071 public final SortDefinition getSortDefinition() { 072 return this.sortDefinition; 073 } 074 075 076 @Override 077 @SuppressWarnings("unchecked") 078 public int compare(T o1, T o2) { 079 Object v1 = getPropertyValue(o1); 080 Object v2 = getPropertyValue(o2); 081 if (this.sortDefinition.isIgnoreCase() && (v1 instanceof String) && (v2 instanceof String)) { 082 v1 = ((String) v1).toLowerCase(); 083 v2 = ((String) v2).toLowerCase(); 084 } 085 086 int result; 087 088 // Put an object with null property at the end of the sort result. 089 try { 090 if (v1 != null) { 091 result = (v2 != null ? ((Comparable<Object>) v1).compareTo(v2) : -1); 092 } 093 else { 094 result = (v2 != null ? 1 : 0); 095 } 096 } 097 catch (RuntimeException ex) { 098 if (logger.isDebugEnabled()) { 099 logger.debug("Could not sort objects [" + o1 + "] and [" + o2 + "]", ex); 100 } 101 return 0; 102 } 103 104 return (this.sortDefinition.isAscending() ? result : -result); 105 } 106 107 /** 108 * Get the SortDefinition's property value for the given object. 109 * @param obj the object to get the property value for 110 * @return the property value 111 */ 112 @Nullable 113 private Object getPropertyValue(Object obj) { 114 // If a nested property cannot be read, simply return null 115 // (similar to JSTL EL). If the property doesn't exist in the 116 // first place, let the exception through. 117 try { 118 this.beanWrapper.setWrappedInstance(obj); 119 return this.beanWrapper.getPropertyValue(this.sortDefinition.getProperty()); 120 } 121 catch (BeansException ex) { 122 logger.debug("PropertyComparator could not access property - treating as null for sorting", ex); 123 return null; 124 } 125 } 126 127 128 /** 129 * Sort the given List according to the given sort definition. 130 * <p>Note: Contained objects have to provide the given property 131 * in the form of a bean property, i.e. a getXXX method. 132 * @param source the input List 133 * @param sortDefinition the parameters to sort by 134 * @throws java.lang.IllegalArgumentException in case of a missing propertyName 135 */ 136 public static void sort(List<?> source, SortDefinition sortDefinition) throws BeansException { 137 if (StringUtils.hasText(sortDefinition.getProperty())) { 138 source.sort(new PropertyComparator<>(sortDefinition)); 139 } 140 } 141 142 /** 143 * Sort the given source according to the given sort definition. 144 * <p>Note: Contained objects have to provide the given property 145 * in the form of a bean property, i.e. a getXXX method. 146 * @param source input source 147 * @param sortDefinition the parameters to sort by 148 * @throws java.lang.IllegalArgumentException in case of a missing propertyName 149 */ 150 public static void sort(Object[] source, SortDefinition sortDefinition) throws BeansException { 151 if (StringUtils.hasText(sortDefinition.getProperty())) { 152 Arrays.sort(source, new PropertyComparator<>(sortDefinition)); 153 } 154 } 155 156}