View Javadoc
1 /* 2 * Scope: a generic MVC framework. 3 * Copyright (c) 2000-2002, The Scope team 4 * All rights reserved. 5 * 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * Neither the name "Scope" nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * 36 * $Id: Selector.java,v 1.10 2002/09/12 18:23:54 ludovicc Exp $ 37 */ 38 package org.scopemvc.core; 39 40 41 import java.util.StringTokenizer; 42 import org.apache.commons.logging.Log; 43 import org.apache.commons.logging.LogFactory; 44 import org.scopemvc.util.Debug; 45 46 /*** 47 * <P> 48 * 49 * An identifier for model properties. Selectors are created by the factory 50 * methods {@link #fromString} and {@link #fromInt}. Properties can be 51 * identified by a String name (eg the "address" property of a Customer) or an 52 * integer index (eg element 1 of a List). </P> <P> 53 * 54 * Selectors can be assembled in a list to identify a property in a model 55 * contained within another model. For example, the <CODE>name</CODE> of the 56 * <CODE>pet</CODE> of a <CODE>Person</CODE>. This Selector would be created by 57 * <CODE>Selector.fromString("pet.name")</CODE> and applied to a Person model 58 * object. Similarly, the name of a Person's first pet could be identified using 59 * <CODE>Selector.fromString("pets.0.name")</CODE> assuming that Person contains 60 * a List or array of Pets that contain a name property. </P> 61 * 62 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A> 63 * @created 05 August 2002 64 * @version $Revision: 1.10 $ $Date: 2002/09/12 18:23:54 $ 65 */ 66 public abstract class Selector { 67 68 /*** 69 * Separator character between Selectors expressed as a String. 70 * 71 * @see #fromString 72 * @see #asString 73 */ 74 public static final String DELIMITER = "."; 75 76 private static final Log LOG = LogFactory.getLog(Selector.class); 77 78 /*** 79 * Link to next Selector in the list. 80 */ 81 private Selector next; 82 83 84 /*** 85 * Package private ctor for the factory. Application code should use {@link 86 * Selector#fromString} or {@link Selector#fromInt} to create Selectors. 87 */ 88 Selector() { } 89 90 91 // -------------------- Factory ------------------------- 92 93 /*** 94 * Make a simple Selector to identify a property at an int index. eg this 95 * returns a Selector that identifies the first element of a List: <PRE> 96 * return Selector.fromInt(0); 97 * </PRE> 98 * 99 * @param inIndex The index of the property in a List 100 * @return A selector identifying a property in a List 101 */ 102 public static IntIndexSelector fromInt(int inIndex) { 103 return new IntIndexSelector(inIndex); 104 } 105 106 107 /*** 108 * Make a Selector to identify a property by its String name, or to create a 109 * Selector list that identifies a property by navigating a hierarchy of 110 * submodels. For example: 111 * <UL> 112 * <LI> <CODE>Selector.fromString("name");</CODE> will identify the name 113 * property of a Person when applied to a Person model object. </LI> 114 * <CODE>Selector.fromString("pet.0.name");</CODE> will identify the name 115 * property of the first Pet of a Person when applied to a Person model 116 * object. 117 * </UL> 118 * 119 * 120 * @param inSelectorDescription The string representation of a Selector 121 * @return A selector identifying a property 122 */ 123 public static Selector fromString(String inSelectorDescription) { 124 if (LOG.isDebugEnabled()) { 125 LOG.debug("fromString: " + inSelectorDescription); 126 } 127 128 StringTokenizer tokenizer = new StringTokenizer(inSelectorDescription, DELIMITER); 129 Selector result = null; 130 131 while (tokenizer.hasMoreTokens()) { 132 String nextToken = tokenizer.nextToken(); 133 // Empty property descriptor is a null Selector 134 if (nextToken.length() < 1) { 135 continue; 136 } 137 138 Selector nextSelector = null; 139 // OK then, does it parse as an int so we get an IntIndexSelector? 140 // TODO: this is a bit nasty. could use [0] notation to mark this? 141 try { 142 int intIndex = Integer.parseInt(nextToken); 143 nextSelector = new IntIndexSelector(intIndex); 144 } catch (NumberFormatException e) { 145 // OK then so make a StringIndexSelector 146 nextSelector = new StringIndexSelector(nextToken); 147 } 148 149 if (LOG.isDebugEnabled()) { 150 LOG.debug("fromString: nextToken " + nextToken + ": nextSelector " + nextSelector); 151 } 152 if (Debug.ON) { 153 Debug.assertTrue(nextSelector != null, "Couldn't create Selector for: " + nextToken); 154 } 155 156 if (result == null) { 157 result = nextSelector; 158 } else { 159 result.chain(nextSelector); 160 } 161 if (LOG.isDebugEnabled()) { 162 LOG.debug("fromString: result: " + result); 163 } 164 } 165 return result; 166 } 167 168 169 /*** 170 * Flatten the Selector list to a String that could be passed into {@link 171 * #fromString} to recreate it. 172 * 173 * @param inSelector flatten this Selector to a String representation. 174 * @return String representation of the passed Selector suitable for passing 175 * back into {@link #fromString} to recreate the Selector list. Return 176 * "" for null Selector. 177 */ 178 public static String asString(Selector inSelector) { 179 if (inSelector == null) { 180 return ""; 181 } 182 183 StringBuffer result = new StringBuffer(); 184 Selector current = inSelector; 185 while (current != null) { 186 result.append(current.getName()); 187 result.append(DELIMITER); 188 current = current.getNext(); 189 } 190 return result.substring(0, result.length() - DELIMITER.length()); 191 } 192 193 194 /*** 195 * Get the next Selector in the list, if any. For example, <PRE> 196 * Selector petNameSelector = Selector.fromString("pet.name"); 197 * return petNameSelector.getNext(); 198 * </PRE> returns a Selector that is <CODE>equals()</CODE> the following 199 * Selector: <PRE> 200 * Selector.fromString("name"); 201 * </PRE> 202 * 203 * @return The next value 204 */ 205 public final Selector getNext() { 206 return next; 207 } 208 209 210 /*** 211 * Get the last Selector in the list. For example, <PRE> 212 * Selector petNameSelector = Selector.fromString("pets.1.name"); 213 * return petNameSelector.getLast(); 214 * </PRE> returns a Selector that is <CODE>equals()</CODE> the following 215 * Selector: <PRE> 216 * Selector.fromString("name"); 217 * </PRE> 218 * 219 * @return The last value 220 */ 221 public final Selector getLast() { 222 Selector result = this; 223 while (result.getNext() != null) { 224 result = result.getNext(); 225 } 226 227 if (Debug.ON) { 228 Debug.assertTrue(result != null); 229 } 230 return result; 231 } 232 233 234 /*** 235 * Used to serialise Selectors {@link #asString} and for debug by {@link 236 * #toString}. 237 * 238 * @return The name value 239 * @todo public access for test cases -- this is nasty. 240 */ 241 public abstract String getName(); 242 243 244 /*** 245 * <P> 246 * 247 * Add a Selector on the end of this list. </P> <P> 248 * 249 * For example: <PRE> 250 * Selector petSelector = Selector.fromString("pet"); 251 * Selector nameSelector = Selector.fromString("name"); 252 * petSelector.chain(nameSelector); 253 * return petSelector; 254 * </PRE> returns a Selector that is <CODE>equals()</CODE> this one: <PRE> 255 * Selector.fromString("pet.name"); 256 * </PRE> </P> 257 * 258 * @param inSelector The Selector to chain at the end of the current 259 * Selector 260 */ 261 public final void chain(Selector inSelector) { 262 if (inSelector == null) { 263 throw new IllegalArgumentException("Can't chain a null Selector."); 264 } 265 266 ((Selector) getLast()).setNext(inSelector); 267 } 268 269 270 /*** 271 * Remove the terminal Selector. Throws UnsupportedOperationException if 272 * Selector has no chain. 273 */ 274 public final void removeLast() { 275 if (getNext() == null) { 276 throw new UnsupportedOperationException("No terminal Selector to remove"); 277 } 278 Selector penultimate = this; 279 while (penultimate.getNext().getNext() != null) { 280 penultimate = penultimate.getNext(); 281 } 282 penultimate.setNext(null); 283 } 284 285 286 /*** 287 * Remove the terminal Selector. Throws UnsupportedOperationException if 288 * Selector has no chain or does no ends with the passed terminal selector. 289 * 290 * @param terminalSelector TODO: Describe the Parameter 291 */ 292 public final void removeLast(Selector terminalSelector) { 293 if (getNext() == null) { 294 throw new UnsupportedOperationException("No terminal Selector to remove"); 295 } 296 Selector beforeTerminal = this; 297 while (beforeTerminal.getNext().getNext() != null && !beforeTerminal.getNext().equals(terminalSelector)) { 298 beforeTerminal = beforeTerminal.getNext(); 299 } 300 if (!beforeTerminal.getNext().equals(terminalSelector)) { 301 throw new UnsupportedOperationException("Terminal Selector " + terminalSelector + " doesn't end Selector " + this); 302 } 303 beforeTerminal.setNext(null); 304 } 305 306 /*** 307 * <P> 308 * 309 * Does this Selector list start with the list passed in? </P> <P> 310 * 311 * For example, this returns <CODE>true</CODE>: <PRE> 312 * Selector petSelector = Selector.fromString("pet"); 313 * Selector petNameSelector = Selector.fromString("pet.name"); 314 * return (petNameSelector.startsWith(petSelector)); 315 * </PRE> but this returns <CODE>false</CODE>: <PRE> 316 * Selector nameSelector = Selector.fromString("name"); 317 * Selector petNameSelector = Selector.fromString("pet.name"); 318 * return (petNameSelector.startsWith(nameSelector)); 319 * </PRE> </P> 320 * 321 * @param inSelector A Selector 322 * @return True if this Selector list start with the list passed in 323 */ 324 public final boolean startsWith(Selector inSelector) { 325 Selector matchedTarget = inSelector; 326 Selector matchedThis = this; 327 while (matchedTarget != null) { 328 if (matchedThis == null) { 329 // passed in is longer than this 330 return false; 331 } 332 if (matchedThis.shallowEquals(matchedTarget)) { 333 matchedThis = matchedThis.getNext(); 334 matchedTarget = matchedTarget.getNext(); 335 } else { 336 // No match 337 return false; 338 } 339 } 340 return true; 341 } 342 343 344 /*** 345 * <P> 346 * 347 * A deep compare, following down the list of selectors. </P> <P> 348 * 349 * For example, this returns <CODE>true</CODE>: <PRE> 350 * Selector petSelector = Selector.fromString("pet"); 351 * Selector nameSelector = Selector.fromString("name"); 352 * Selector petNameSelector = Selector.fromString("pet.name"); 353 * petSelector.chain(nameSelector); 354 * return (petSelector.equals(petNameSelector)); 355 * </PRE> but this returns <CODE>false</CODE>: <PRE> 356 * Selector petSelector = Selector.fromString("pet"); 357 * Selector petNameSelector = Selector.fromString("pet.name"); 358 * return (petSelector.equals(petNameSelector)); 359 * </PRE> </P> 360 * 361 * @param inObject An Object to test for equality, mostly another Selector 362 * @return true if this Selector and the passed object are equal, including 363 * the chained Selectors in the list. 364 */ 365 public final boolean equals(Object inObject) { 366 367 // Trivial case: same object 368 if (inObject == this) { 369 return true; 370 } 371 372 // null or wrong class 373 if (!(inObject instanceof Selector)) { 374 return false; 375 } 376 377 // try the shallow equals 378 Selector inSelector = (Selector) inObject; 379 if (!shallowEquals(inSelector)) { 380 return false; 381 } 382 383 if (getNext() == null) { 384 if (inSelector.getNext() == null) { 385 return true; 386 } 387 return false; 388 } 389 390 // and recurse down the list 391 return getNext().equals(inSelector.getNext()); 392 } 393 394 395 /*** 396 * Return a clone of the entire list of Selectors from <CODE>this</CODE>. 397 * 398 * @return A complete close of this Selector. 399 * @post result.equals(this) 400 */ 401 public final Selector deepClone() { 402 Selector result = getShallowCopy(); 403 if (getNext() != null) { 404 result.chain(getNext().deepClone()); 405 } 406 return result; 407 } 408 409 410 // ------------------- Debug ----------------------- 411 412 /*** 413 * For debug. 414 * 415 * @return A string representation of this Selector 416 */ 417 public final String toString() { 418 StringBuffer result = new StringBuffer(); 419 Package p = getClass().getPackage(); 420 if (p == null) { 421 result.append(getClass().getName()); 422 } else { 423 result.append(getClass().getName().substring(p.getName().length() + 1)); 424 } 425 result.append("("); 426 result.append(Selector.asString(this)); 427 result.append(")"); 428 return result.toString(); 429 } 430 431 432 /*** 433 * Return a shallow copy of the head of <CODE>this</CODE>. 434 * 435 * @return The shallowCopy value 436 */ 437 protected abstract Selector getShallowCopy(); 438 439 440 /*** 441 * Set the next Selector in the list. 442 * 443 * @param inSelector Selector to set as the next in the list after <CODE>this</CODE> 444 * @see #chain 445 */ 446 protected final void setNext(Selector inSelector) { 447 next = inSelector; 448 } 449 450 451 /*** 452 * Compare the head Selector of <CODE>this</CODE> against the head of 453 * another Selector list -- ie a shallow compare operation (not including 454 * the chained selectors). 455 * 456 * @param inSelector The Selector to test 457 * @return true if there is equality between this Selector and the other 458 * Selector, excluding the other chained Selectors. 459 */ 460 protected abstract boolean shallowEquals(Selector inSelector); 461 } 462

This page was automatically generated by Maven