001package org.andromda.utils.beans.comparators; 002 003import java.io.InputStream; 004import java.io.Serializable; 005import java.lang.reflect.InvocationTargetException; 006import java.net.URL; 007import java.util.Comparator; 008import java.util.Properties; 009 010import org.andromda.core.common.ClassUtils; 011import org.andromda.core.common.ExceptionUtils; 012import org.andromda.core.common.Introspector; 013import org.andromda.utils.beans.BeanSorter; 014import org.andromda.utils.beans.SortCriteria; 015import org.apache.commons.lang.StringUtils; 016 017/** 018 * Used by BeanSorter to provide sorting capabilities for 019 * beans. Currently supports sorting by all simple properties 020 * 021 * @see BeanSorter 022 * 023 * @author Chad Brandon 024 */ 025public class BeanComparator 026 implements Comparator, 027 Serializable 028{ 029 private static final long serialVersionUID = 34L; 030 031 /** 032 * Stores the comparator mappings. 033 */ 034 private static final Properties comparators = new Properties(); 035 036 static 037 { 038 InputStream stream = null; 039 try 040 { 041 final String comparatorsFile = "/Comparators.properties"; 042 final URL comparatorsUri = BeanComparator.class.getResource(comparatorsFile); 043 if (comparatorsUri == null) 044 { 045 throw new BeanComparatorException("The comparators resource '" + comparatorsFile + 046 " could not be loaded"); 047 } 048 stream = comparatorsUri.openStream(); 049 comparators.load(stream); 050 } 051 catch (final Throwable throwable) 052 { 053 throw new RuntimeException(throwable); 054 } 055 finally 056 { 057 if (stream != null) try {stream.close();} catch (Exception ex) {} 058 stream = null; 059 } 060 } 061 062 private Comparator comparator = null; 063 private SortCriteria sortCriteria; 064 065 /** 066 * @param sortCriteria 067 */ 068 public BeanComparator(SortCriteria sortCriteria) 069 { 070 ExceptionUtils.checkNull( 071 "sortCriteria", 072 sortCriteria); 073 this.sortCriteria = sortCriteria; 074 } 075 076 /** 077 * @see java.util.Comparator#compare(Object, Object) 078 */ 079 @Override 080 public int compare( 081 final Object objectA, 082 Object objectB) 083 { 084 ExceptionUtils.checkNull( 085 "objectA", 086 objectA); 087 ExceptionUtils.checkNull( 088 "objectB", 089 objectB); 090 091 try 092 { 093 Object aValue; 094 Object bValue; 095 if (this.sortCriteria.getOrdering().equals(SortCriteria.Ordering.DESCENDING)) 096 { 097 // - since its descending switch the objects we are getting the values from, 098 // so the order will be reversed 099 aValue = getProperty( 100 objectB, 101 sortCriteria.getSortBy()); 102 bValue = getProperty( 103 objectA, 104 sortCriteria.getSortBy()); 105 } 106 else 107 { 108 // - otherwise we assume its ascending 109 aValue = getProperty( 110 objectA, 111 sortCriteria.getSortBy()); 112 bValue = getProperty( 113 objectB, 114 sortCriteria.getSortBy()); 115 } 116 117 //we default result to zero (zero result would mean aValue and bValue equal each other) 118 int result = 0; 119 120 //first sort for null values, null values will always come last 121 if (aValue != null || bValue != null) 122 { 123 if (aValue == null) 124 { 125 if (sortCriteria.isNullsFirst()) 126 { 127 result = -1; 128 } 129 else 130 { 131 result = 1; 132 } 133 } 134 else if (bValue == null) 135 { 136 if (sortCriteria.isNullsFirst()) 137 { 138 result = 1; 139 } 140 else 141 { 142 result = -1; 143 } 144 } 145 else 146 { 147 result = this.getComparator(aValue.getClass()).compare( 148 aValue, 149 bValue); 150 } 151 } 152 return result; 153 } 154 catch (final Throwable throwable) 155 { 156 throw new ComparatorException(throwable); 157 } 158 } 159 160 /** 161 * Gets the property specified by propertyName from the bean. 162 * Checks each nested parent to see if its null and if it is 163 * returns null. 164 * @param bean 165 * @param propertyName 166 * @return 167 */ 168 private Object getProperty( 169 final Object bean, 170 String propertyName) 171 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException 172 { 173 Object value = null; 174 if (bean != null && StringUtils.isNotBlank(propertyName)) 175 { 176 int index = getNestedIndex(propertyName); 177 if (index != -1) 178 { 179 String simpleProp = propertyName.substring( 180 0, 181 index); 182 value = Introspector.instance().getProperty( 183 bean, 184 simpleProp); 185 if (value != null) 186 { 187 if (getNestedIndex(propertyName) != -1) 188 { 189 propertyName = propertyName.substring( 190 index + 1, 191 propertyName.length()); 192 value = getProperty( 193 value, 194 propertyName); 195 } 196 } 197 } 198 else 199 { 200 value = Introspector.instance().getProperty( 201 bean, 202 propertyName); 203 } 204 } 205 return value; 206 } 207 208 /** 209 * Gets the index of the given <code>propertyName</code> if 210 * its a nested property (nested meaning names separated by 211 * a '.'). 212 * @param propertyName the name of the nested property. 213 * @return the index. 214 */ 215 private final int getNestedIndex(final String propertyName) 216 { 217 int index = -1; 218 if (StringUtils.isNotBlank(propertyName)) 219 { 220 index = propertyName.indexOf('.'); 221 } 222 return index; 223 } 224 225 /** 226 * Retrieves the associated Comparator for the type 227 * @param type the class of which to retrieve the comparator. 228 * @return appropriate comparator or null if one wasn't defined. 229 */ 230 private final Comparator getComparator(final Class type) 231 { 232 try 233 { 234 if (this.comparator == null) 235 { 236 final String comparatorName = findComparatorName(type); 237 if (StringUtils.isNotBlank(comparatorName)) 238 { 239 this.comparator = (Comparator)ClassUtils.loadClass(comparatorName).newInstance(); 240 } 241 else 242 { 243 throw new ComparatorException("No comparator defined for the given type '" + type.getName() + '\''); 244 } 245 } 246 return this.comparator; 247 } 248 catch (final Throwable throwable) 249 { 250 throw new ComparatorException(throwable); 251 } 252 } 253 254 private String findComparatorName(final Class type) 255 { 256 String comparatorName = comparators.getProperty(type.getName()); 257 if (StringUtils.isEmpty(comparatorName) && type.getSuperclass() != null) 258 { 259 comparatorName = findComparatorName(type.getSuperclass()); 260 } 261 return comparatorName; 262 } 263 264 /** 265 * Returns the current sortCriteria value 266 * @return String 267 */ 268 public SortCriteria getSortCriteria() 269 { 270 return this.sortCriteria; 271 } 272 273 /** 274 * Sets the new sortCriteria value 275 * @param sortCriteria 276 */ 277 public void setSortCriteria(final SortCriteria sortCriteria) 278 { 279 ExceptionUtils.checkNull( 280 "sortCriteria", 281 sortCriteria); 282 this.sortCriteria = sortCriteria; 283 } 284}