Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/scopemvc/core/Selector.java


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