001package com.avaje.ebean; 002 003import java.io.Serializable; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.List; 007 008/** 009 * Represents an Order By for a Query. 010 * <p> 011 * Is a ordered list of OrderBy.Property objects each specifying a property and 012 * whether it is ascending or descending order. 013 * </p> 014 * <p> 015 * Typically you will not construct an OrderBy yourself but use one that exists 016 * on the Query object. 017 * </p> 018 */ 019public final class OrderBy<T> implements Serializable { 020 021 private static final long serialVersionUID = 9157089257745730539L; 022 023 private transient Query<T> query; 024 025 private final List<Property> list; 026 027 /** 028 * Create an empty OrderBy with no associated query. 029 */ 030 public OrderBy() { 031 this.list = new ArrayList<Property>(3); 032 } 033 034 private OrderBy(List<Property> list) { 035 this.list = list; 036 } 037 038 /** 039 * Create an orderBy parsing the order by clause. 040 * <p> 041 * The order by clause follows SQL order by clause with comma's between each 042 * property and optionally "asc" or "desc" to represent ascending or 043 * descending order respectively. 044 * </p> 045 */ 046 public OrderBy(String orderByClause) { 047 this(null, orderByClause); 048 } 049 050 /** 051 * Construct with a given query and order by clause. 052 */ 053 public OrderBy(Query<T> query, String orderByClause) { 054 this.query = query; 055 this.list = new ArrayList<Property>(3); 056 parse(orderByClause); 057 } 058 059 /** 060 * Reverse the ascending/descending order on all the properties. 061 */ 062 public void reverse() { 063 for (int i = 0; i < list.size(); i++) { 064 list.get(i).reverse(); 065 } 066 } 067 068 /** 069 * Add a property with ascending order to this OrderBy. 070 */ 071 public Query<T> asc(String propertyName) { 072 073 list.add(new Property(propertyName, true)); 074 return query; 075 } 076 077 /** 078 * Add a property with descending order to this OrderBy. 079 */ 080 public Query<T> desc(String propertyName) { 081 082 list.add(new Property(propertyName, false)); 083 return query; 084 } 085 086 /** 087 * Return true if the property is known to be contained in the order by clause. 088 */ 089 public boolean containsProperty(String propertyName) { 090 091 for (int i = 0; i < list.size(); i++) { 092 if (propertyName.equals(list.get(i).getProperty())) { 093 return true; 094 } 095 } 096 return false; 097 } 098 099 /** 100 * Return a copy of this OrderBy with the path trimmed. 101 */ 102 public OrderBy<T> copyWithTrim(String path) { 103 List<Property> newList = new ArrayList<Property>(list.size()); 104 for (int i = 0; i < list.size(); i++) { 105 newList.add(list.get(i).copyWithTrim(path)); 106 } 107 return new OrderBy<T>(newList); 108 } 109 110 /** 111 * Return the properties for this OrderBy. 112 */ 113 public List<Property> getProperties() { 114 // not returning an Immutable list at this point 115 return list; 116 } 117 118 /** 119 * Return true if this OrderBy does not have any properties. 120 */ 121 public boolean isEmpty() { 122 return list.isEmpty(); 123 } 124 125 /** 126 * Return the associated query if there is one. 127 */ 128 public Query<T> getQuery() { 129 return query; 130 } 131 132 /** 133 * Associate this OrderBy with a query. 134 */ 135 public void setQuery(Query<T> query) { 136 this.query = query; 137 } 138 139 /** 140 * Return a copy of the OrderBy. 141 */ 142 public OrderBy<T> copy() { 143 144 OrderBy<T> copy = new OrderBy<T>(); 145 for (int i = 0; i < list.size(); i++) { 146 copy.add(list.get(i).copy()); 147 } 148 return copy; 149 } 150 151 /** 152 * Add a property to the order by. 153 */ 154 public void add(Property p) { 155 list.add(p); 156 } 157 158 public String toString() { 159 return list.toString(); 160 } 161 162 /** 163 * Returns the OrderBy in string format. 164 */ 165 public String toStringFormat() { 166 if (list.isEmpty()) { 167 return null; 168 } 169 StringBuilder sb = new StringBuilder(); 170 for (int i = 0; i < list.size(); i++) { 171 Property property = list.get(i); 172 if (i > 0) { 173 sb.append(", "); 174 } 175 sb.append(property.toStringFormat()); 176 } 177 return sb.toString(); 178 } 179 180 @Override 181 public boolean equals(Object obj) { 182 if (obj == this) { 183 return true; 184 } 185 if (!(obj instanceof OrderBy<?>)) { 186 return false; 187 } 188 189 OrderBy<?> e = (OrderBy<?>) obj; 190 return e.list.equals(list); 191 } 192 193 /** 194 * Return a hash value for this OrderBy. This can be to determine logical 195 * equality for OrderBy clauses. 196 */ 197 public int hashCode() { 198 return list.hashCode(); 199 } 200 201 /** 202 * Clear the orderBy removing any current order by properties. 203 * <p> 204 * This is intended to be used when some code creates a query with a 205 * 'default' order by clause and some other code may clear the 'default' 206 * order by clause and replace. 207 * </p> 208 */ 209 public void clear() { 210 list.clear(); 211 } 212 213 /** 214 * A property and its ascending descending order. 215 */ 216 public static final class Property implements Serializable { 217 218 private static final long serialVersionUID = 1546009780322478077L; 219 220 private String property; 221 222 private boolean ascending; 223 224 public Property(String property, boolean ascending) { 225 this.property = property; 226 this.ascending = ascending; 227 } 228 229 /** 230 * Return a copy of this Property with the path trimmed. 231 */ 232 public Property copyWithTrim(String path) { 233 return new Property(property.substring(path.length() + 1), ascending); 234 } 235 236 public int hashCode() { 237 int hc = property.hashCode(); 238 hc = hc * 31 + (ascending ? 0 : 1); 239 return hc; 240 } 241 242 public boolean equals(Object obj) { 243 if (obj == this) { 244 return true; 245 } 246 if (!(obj instanceof Property)) { 247 return false; 248 } 249 250 Property e = (Property) obj; 251 return e.ascending == ascending 252 && e.property.equals(property); 253 } 254 255 public String toString() { 256 return toStringFormat(); 257 } 258 259 public String toStringFormat() { 260 if (ascending) { 261 return property; 262 } else { 263 return property + " desc"; 264 } 265 } 266 267 /** 268 * Reverse the ascending/descending order for this property. 269 */ 270 public void reverse() { 271 this.ascending = !ascending; 272 } 273 274 /** 275 * Trim off the pathPrefix. 276 */ 277 public void trim(String pathPrefix) { 278 property = property.substring(pathPrefix.length() + 1); 279 } 280 281 /** 282 * Return a copy of this property. 283 */ 284 public Property copy() { 285 return new Property(property, ascending); 286 } 287 288 /** 289 * Return the property name. 290 */ 291 public String getProperty() { 292 return property; 293 } 294 295 /** 296 * Set the property name. 297 */ 298 public void setProperty(String property) { 299 this.property = property; 300 } 301 302 /** 303 * Return true if the order is ascending. 304 */ 305 public boolean isAscending() { 306 return ascending; 307 } 308 309 /** 310 * Set to true if the order is ascending. 311 */ 312 public void setAscending(boolean ascending) { 313 this.ascending = ascending; 314 } 315 316 } 317 318 private void parse(String orderByClause) { 319 320 if (orderByClause == null) { 321 return; 322 } 323 324 String[] chunks = orderByClause.split(","); 325 for (int i = 0; i < chunks.length; i++) { 326 327 String[] pairs = chunks[i].split(" "); 328 Property p = parseProperty(pairs); 329 if (p != null) { 330 list.add(p); 331 } 332 } 333 } 334 335 private Property parseProperty(String[] pairs) { 336 if (pairs.length == 0) { 337 return null; 338 } 339 340 ArrayList<String> wordList = new ArrayList<String>(pairs.length); 341 for (int i = 0; i < pairs.length; i++) { 342 if (!isEmptyString(pairs[i])) { 343 wordList.add(pairs[i]); 344 } 345 } 346 if (wordList.isEmpty()) { 347 return null; 348 } 349 if (wordList.size() == 1) { 350 return new Property(wordList.get(0), true); 351 } 352 if (wordList.size() == 2) { 353 boolean asc = isAscending(wordList.get(1)); 354 return new Property(wordList.get(0), asc); 355 } 356 String m = "Expecting a max of 2 words in [" + Arrays.toString(pairs) 357 + "] but got " + wordList.size(); 358 throw new RuntimeException(m); 359 } 360 361 private boolean isAscending(String s) { 362 s = s.toLowerCase(); 363 if (s.startsWith("asc")) { 364 return true; 365 } 366 if (s.startsWith("desc")) { 367 return false; 368 } 369 String m = "Expecting [" + s + "] to be asc or desc?"; 370 throw new RuntimeException(m); 371 } 372 373 private boolean isEmptyString(String s) { 374 return s == null || s.length() == 0; 375 } 376}