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 to the order by by parsing a raw expression. 153 */ 154 public void add(String rawExpression) { 155 parse(rawExpression); 156 } 157 158 /** 159 * Add a property to the order by. 160 */ 161 public void add(Property p) { 162 list.add(p); 163 } 164 165 public String toString() { 166 return list.toString(); 167 } 168 169 /** 170 * Returns the OrderBy in string format. 171 */ 172 public String toStringFormat() { 173 if (list.isEmpty()) { 174 return null; 175 } 176 StringBuilder sb = new StringBuilder(); 177 for (int i = 0; i < list.size(); i++) { 178 Property property = list.get(i); 179 if (i > 0) { 180 sb.append(", "); 181 } 182 sb.append(property.toStringFormat()); 183 } 184 return sb.toString(); 185 } 186 187 @Override 188 public boolean equals(Object obj) { 189 if (obj == this) { 190 return true; 191 } 192 if (!(obj instanceof OrderBy<?>)) { 193 return false; 194 } 195 196 OrderBy<?> e = (OrderBy<?>) obj; 197 return e.list.equals(list); 198 } 199 200 /** 201 * Return a hash value for this OrderBy. This can be to determine logical 202 * equality for OrderBy clauses. 203 */ 204 public int hashCode() { 205 return list.hashCode(); 206 } 207 208 /** 209 * Clear the orderBy removing any current order by properties. 210 * <p> 211 * This is intended to be used when some code creates a query with a 212 * 'default' order by clause and some other code may clear the 'default' 213 * order by clause and replace. 214 * </p> 215 */ 216 public OrderBy<T> clear() { 217 list.clear(); 218 return this; 219 } 220 221 /** 222 * A property and its ascending descending order. 223 */ 224 public static final class Property implements Serializable { 225 226 private static final long serialVersionUID = 1546009780322478077L; 227 228 private String property; 229 230 private boolean ascending; 231 232 private String nulls; 233 234 private String highLow; 235 236 public Property(String property, boolean ascending) { 237 this.property = property; 238 this.ascending = ascending; 239 } 240 241 public Property(String property, boolean ascending, String nulls, String highLow) { 242 this.property = property; 243 this.ascending = ascending; 244 this.nulls = nulls; 245 this.highLow = highLow; 246 } 247 248 /** 249 * Return a copy of this Property with the path trimmed. 250 */ 251 public Property copyWithTrim(String path) { 252 return new Property(property.substring(path.length() + 1), ascending, nulls, highLow); 253 } 254 255 @Override 256 public int hashCode() { 257 int hc = property.hashCode(); 258 hc = hc * 31 + (ascending ? 0 : 1); 259 hc = hc * 31 + (nulls == null ? 0 : nulls.hashCode()); 260 hc = hc * 31 + (highLow == null ? 0 : highLow.hashCode()); 261 return hc; 262 } 263 264 @Override 265 public boolean equals(Object obj) { 266 if (obj == this) { 267 return true; 268 } 269 if (!(obj instanceof Property)) { 270 return false; 271 } 272 Property e = (Property) obj; 273 if (ascending != e.ascending) return false; 274 if (!property.equals(e.property)) return false; 275 if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false; 276 return highLow != null ? highLow.equals(e.highLow) : e.highLow == null; 277 } 278 279 public String toString() { 280 return toStringFormat(); 281 } 282 283 public String toStringFormat() { 284 if (nulls == null) { 285 if (ascending) { 286 return property; 287 } else { 288 return property + " desc"; 289 } 290 } else { 291 StringBuilder sb = new StringBuilder(); 292 sb.append(property); 293 if (!ascending) { 294 sb.append(" ").append("desc"); 295 } 296 sb.append(" ").append(nulls).append(" ").append(highLow); 297 return sb.toString(); 298 } 299 } 300 301 /** 302 * Reverse the ascending/descending order for this property. 303 */ 304 public void reverse() { 305 this.ascending = !ascending; 306 } 307 308 /** 309 * Trim off the pathPrefix. 310 */ 311 public void trim(String pathPrefix) { 312 property = property.substring(pathPrefix.length() + 1); 313 } 314 315 /** 316 * Return a copy of this property. 317 */ 318 public Property copy() { 319 return new Property(property, ascending, nulls, highLow); 320 } 321 322 /** 323 * Return the property name. 324 */ 325 public String getProperty() { 326 return property; 327 } 328 329 /** 330 * Set the property name. 331 */ 332 public void setProperty(String property) { 333 this.property = property; 334 } 335 336 /** 337 * Return true if the order is ascending. 338 */ 339 public boolean isAscending() { 340 return ascending; 341 } 342 343 /** 344 * Set to true if the order is ascending. 345 */ 346 public void setAscending(boolean ascending) { 347 this.ascending = ascending; 348 } 349 350 } 351 352 private void parse(String orderByClause) { 353 354 if (orderByClause == null) { 355 return; 356 } 357 358 String[] chunks = orderByClause.split(","); 359 for (int i = 0; i < chunks.length; i++) { 360 String[] pairs = chunks[i].split(" "); 361 Property p = parseProperty(pairs); 362 if (p != null) { 363 list.add(p); 364 } 365 } 366 } 367 368 private Property parseProperty(String[] pairs) { 369 if (pairs.length == 0) { 370 return null; 371 } 372 373 ArrayList<String> wordList = new ArrayList<String>(pairs.length); 374 for (int i = 0; i < pairs.length; i++) { 375 if (!isEmptyString(pairs[i])) { 376 wordList.add(pairs[i]); 377 } 378 } 379 if (wordList.isEmpty()) { 380 return null; 381 } 382 if (wordList.size() == 1) { 383 return new Property(wordList.get(0), true); 384 } 385 if (wordList.size() == 2) { 386 boolean asc = isAscending(wordList.get(1)); 387 return new Property(wordList.get(0), asc); 388 } 389 if (wordList.size() == 4) { 390 // nulls high or nulls low as 3rd and 4th 391 boolean asc = isAscending(wordList.get(1)); 392 return new Property(wordList.get(0), asc, wordList.get(2), wordList.get(3)); 393 } 394 String m = "Expecting a 1, 2 or 4 words in [" + Arrays.toString(pairs) + "] but got " + wordList; 395 throw new RuntimeException(m); 396 } 397 398 private boolean isAscending(String s) { 399 s = s.toLowerCase(); 400 if (s.startsWith("asc")) { 401 return true; 402 } 403 if (s.startsWith("desc")) { 404 return false; 405 } 406 String m = "Expecting [" + s + "] to be asc or desc?"; 407 throw new RuntimeException(m); 408 } 409 410 private boolean isEmptyString(String s) { 411 return s == null || s.isEmpty(); 412 } 413}