View Javadoc

1   /* ===================================================================
2    * Copyright 2002-04 SRI International.
3    * Released under the MOZILLA PUBLIC LICENSE Version 1.1
4    * which can be obtained at http://www.mozilla.org/MPL/MPL-1.1.html
5    * This software is distributed on an "AS IS"
6    * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
7    * See the License for the specific language governing rights and
8    * limitations under the License.
9    * =================================================================== */
10  package com.sri.emo.wizard.defaults;
11  
12  import com.sri.emo.wizard.AbstractWizard;
13  import com.sri.emo.wizard.WizardException;
14  import com.sri.emo.wizard.WizardMonitor;
15  import com.sri.emo.wizard.WizardPage;
16  
17  import java.io.Serializable;
18  import java.util.*;
19  
20  
21  /***
22   * Wizard for sequential wizards.  In other words, this is for classes
23   * that have no decision making between each step.
24   *
25   * @author Michael Rimov
26   */
27  public class SequentialWizard extends AbstractWizard {
28  
29      /***
30  	 * 
31  	 */
32  	private static final long serialVersionUID = 1L;
33  
34  	/***
35       * An array of steps.  Since it's sequential, wizSteps[0] == first
36       * step, wizSteps[1] == second step and so on.
37       */
38      final private WizardPage[] wizSteps;
39  
40      /***
41       * Internal boolean flag -- if finished then page history's index will
42       * include the last page.
43       */
44      boolean finished = false;
45  
46      /***
47       * Constructor that takes a monitor and an array of steps.
48       *
49       * @param wizMonitor the monitor for events occuring in the wizard.
50       * @param steps      the WizardPage array ordered by the order
51       *                   they appear.  Index 0 == first page.
52       */
53      public SequentialWizard(final WizardMonitor wizMonitor, final WizardPage[] steps) {
54          super(wizMonitor);
55          wizSteps = steps;
56      }
57  
58      /***
59       * Retrieve the initial page for the wizard.
60       *
61       * @return WizardPage instance.
62       */
63      protected WizardPage getInitialPage() {
64          return wizSteps[0];
65      }
66  
67      /***
68       * Retrieve the index of the current wizard.  First page is page 1,
69       * Second Page is page 2, etc.
70       *
71       * @return int
72       */
73      public int getCurrentPageIndex() {
74          return (findWizardPageIndex(getCurrentPage()) + 1);
75      }
76  
77      /***
78       * Retrieve the total number of pages in this sequential wizard.
79       *
80       * @return int the number of pages.
81       */
82      public int getTotalPages() {
83          return wizSteps.length;
84      }
85  
86      /***
87       * {@inheritDoc}
88       * <p>The SequentialWizard implementation allows for
89       * the browser back button
90       * to be pressed and still achieve status quo.  What happens is that
91       * if we detect that the browser back button was ealier pressed
92       * (we can tell because the src page parameter is already on
93       * the backtrace stack), then we rollback to the page previous to the
94       * src page and re-execute.</p>
95       *
96       * @param src     the wizard page source
97       * @param newData the data for the previous page.
98       * @return the next wizard page
99       * @throws WizardException                upon error
100      * @throws ArrayIndexOutOfBoundsException if next was clicked and
101      *                                        there should be no next button (ie last page)
102      */
103     public WizardPage next(WizardPage src, Serializable newData) throws WizardException {
104         finished = false;
105         getMonitor().onForward(src);
106         src.setData(newData);
107 
108         int val = findWizardPageIndex(src);
109         if (isNeedingRollback(src)) {
110             rollbackBacktrace(src);
111         }
112 
113         //Checks against illegal forward jumps.
114         checkAgainstIllegalJump(src);
115 
116         //
117         //Allow for the page to validate itself first.  If there's problems there
118         //don't even try continuing.
119         //
120         if (src.getPageErrors() != null && src.getPageErrors().size() > 0) {
121             return wizSteps[val];
122         }
123 
124         //Call template method and see if we can continue.
125 
126         if (!onNextPage(wizSteps[val], wizSteps[val + 1], newData)) {
127             //Rollback
128             return wizSteps[val];
129         } else {
130             super.getBackTrace().push(src);
131             val++;
132             getMonitor().onEnterPage(wizSteps[val]);
133             setCurrentPage(wizSteps[val]);
134 
135             return wizSteps[val];
136         }
137     }
138 
139     /***
140      * Forgivingly converts page id to an internal integer.  Allowed parameters
141      * can be either Strings with integers or java.lang.Integer object.
142      *
143      * @param pageId Serializable the requested page id.
144      * @return int the converted value.
145      */
146     protected Integer convertKeyToPageId(Serializable pageId) {
147         assert pageId != null;
148 
149         if (pageId instanceof String) {
150             return new Integer((String) pageId);
151         } else if (pageId instanceof Integer) {
152             return ((Integer) pageId);
153         } else {
154             throw new IllegalArgumentException(
155                     "Page id must be some sort of integer, either in java.lang.Integer or java.lang.String form");
156         }
157     }
158 
159     /***
160      * Allows programmatic rewinding to a previous page.
161      *
162      * @param pageId Serializable the page key.
163      * @return WizardPage the resulting page.
164      */
165     public WizardPage backupToPage(Serializable pageKey) throws ArrayIndexOutOfBoundsException,
166             IllegalArgumentException {
167         assert pageKey != null;
168 
169         Integer pageId = convertKeyToPageId(pageKey);
170         assert pageId.intValue() > 0;
171 
172         WizardPage requestedPage = null;
173         for (int i = 0; i < wizSteps.length; i++) {
174             if (wizSteps[i].getId().equals(pageId)) {
175                 requestedPage = wizSteps[i];
176                 break;
177             }
178         }
179 
180         if (requestedPage == null) {
181             throw new IllegalArgumentException("Could not find page with id: " + pageKey.toString());
182         }
183 
184         rollbackBacktrace(requestedPage);
185         setCurrentPage(requestedPage);
186         getMonitor().onBack(requestedPage);
187 
188         getMonitor().onEnterPage(requestedPage);
189         return requestedPage;
190     }
191 
192 
193     /***
194      * Template method that gets called after the next page has been determined
195      * in the sequence.
196      *
197      * @param previousPage     The previousPage that was displayted
198      * @param nextPage         WizardPage the next page that is about to be displayed.
199      * @param previousPageData Serializable the previously entered data from the
200      *                         last page where the user clicked 'next'.
201      * @return boolean true if you wish for the user to continue.  False if
202      *         the page data needs to be vetoed.
203      * @throws WizardException upon error.
204      */
205     protected boolean onNextPage(final WizardPage previousPage,
206                                  final WizardPage nextPage,
207                                  final Serializable previousPageData) throws WizardException {
208 
209         return (previousPage.getPageErrors() == null);
210     }
211 
212     /***
213      * Checks that there were no illegally jumping forward moves
214      * caused by parameter manipulation.
215      *
216      * @param src WizardPage the wizard page we're checking against.
217      * @throws IllegalStateException if the check fails.
218      */
219     protected void checkAgainstIllegalJump(WizardPage src) {
220         Stack backtrace = getBackTrace();
221         if (backtrace.size() == 0) {
222             if (findWizardPageIndex(src) == 0) {
223                 return;
224             } else {
225                 throw new IllegalStateException("Illegal Page Order");
226             }
227         } else {
228             WizardPage previous = (WizardPage) backtrace.peek();
229             int previousIndex = findWizardPageIndex(previous);
230             int current = findWizardPageIndex(src);
231             if (!((current - 1) == previousIndex)) {
232                 throw new IllegalStateException("Illegal Page Order");
233             }
234         }
235     }
236 
237     /***
238      * {@inheritDoc}
239      *
240      * @param s Serializable
241      * @return WizardPage
242      */
243     public WizardPage getPageById(Serializable s) {
244         for (int i = 0; i < wizSteps.length; i++) {
245             WizardPage onePage = wizSteps[i];
246             if (onePage.getId().equals(s)) {
247                 return onePage;
248             }
249         }
250 
251         throw new IllegalArgumentException("Key: " + s.toString()
252                 + " does not exist for this wizard");
253     }
254 
255     /***
256      * Retrieve all the steps for the wizard.
257      *
258      * @return WizardPage[]
259      */
260     protected WizardPage[] getAllSteps() {
261         return wizSteps;
262     }
263 
264     /***
265      * Checks the backtrace for presence of the given wizard page.
266      *
267      * @param src WizardPage the source wizard page.
268      * @return boolean true.
269      */
270     protected boolean isNeedingRollback(WizardPage src) {
271         Stack backtrace = getBackTrace();
272         boolean foundInStack = false;
273         for (int i = 0; i < backtrace.size(); i++) {
274             WizardPage onePage = (WizardPage) backtrace.get(i);
275             if (src.equals(onePage)) {
276                 foundInStack = true;
277                 break;
278             }
279         }
280 
281         return foundInStack;
282     }
283 
284 
285     /***
286      * Rolls back the backtrace stack if the src page is already
287      * on the stack, indicating that the browser pushed the back button.
288      *
289      * @param src WizardPage the source of the 'next' event, so we rollback
290      *            so that it is no longer on the backtrace.
291      */
292     protected void rollbackBacktrace(final WizardPage src) {
293         Stack backtrace = getBackTrace();
294 
295         while (!backtrace.empty()) {
296             WizardPage onePage = (WizardPage) backtrace.pop();
297             if (src.equals(onePage)) {
298                 break;
299             }
300         }
301     }
302 
303 
304     /***
305      * Find the wizard page index so we can define what the next page is.
306      *
307      * @param toFind the wizard page to find.
308      * @return integer, the index inside wizSteps
309      * @throws IllegalStateException if we're unable to find the page.
310      */
311     protected int findWizardPageIndex(final WizardPage toFind) {
312         for (int i = 0; i < wizSteps.length; i++) {
313             if (wizSteps[i] == toFind) {
314                 return i;
315             }
316         }
317 
318         throw new IllegalStateException("Unable to find Wizard Page");
319     }
320 
321     /***
322      * Retrieve all the data for the wizard keyed by page id.
323      *
324      * @return Map of WizardPage objects keyed by id.
325      * @throws WizardException upon error
326      */
327     public Map getAllData() throws WizardException {
328         Map returnValue = new HashMap(wizSteps.length);
329 
330         for (int i = 0; i < wizSteps.length; i++) {
331             WizardPage oneStep = wizSteps[i];
332             returnValue.put(oneStep.getId(), oneStep.getData());
333         }
334 
335         return returnValue;
336     }
337 
338     /***
339      * {@inheritDoc}
340      *
341      * @return List
342      * @throws WizardException upon retrieval order.
343      */
344     public List getStepHistory() throws WizardException {
345         //Typically
346         int currentIndex = getCurrentPageIndex() - 1;
347 
348         //If we are finished
349         if (finished) {
350             //Include the last page that was not normally
351             //processed by next.
352             currentIndex++;
353         }
354 
355         //Create a new array and copy the appropriate elements
356         //Return the array as a list.
357         WizardPage history[] = new WizardPage[currentIndex];
358         System.arraycopy(wizSteps, 0, history, 0, currentIndex);
359         return Arrays.asList(history);
360     }
361 
362     /***
363      * Override of Object.equals to check field by field.
364      * {@inheritDoc}
365      *
366      * @param parm1 the object SequentialWizard
367      * @return true if the fields are equal.
368      */
369     public boolean equals(final Object parm1) {
370         if (super.equals(parm1)) {
371             SequentialWizard other = (SequentialWizard) parm1;
372             boolean returnValue = true;
373             returnValue &= (wizSteps.length == other.wizSteps.length);
374             if (returnValue) {
375                 for (int i = 0; i < wizSteps.length; i++) {
376                     returnValue &= wizSteps[i].equals(other.wizSteps[i]);
377                 }
378             }
379             return returnValue;
380         } else {
381             return false;
382         }
383     }
384 
385     /***
386      * Returns a hash code value for the object.
387      *
388      * @return a hash code value for this object.
389      */
390     public int hashCode() {
391         return super.hashCode();
392     }
393 
394     /***
395      * Processes the final finish and returns some sort of data that can be
396      * whatever is desired.
397      *
398      * @param src             WizardPage the source of the event.
399      * @param data            Serializable the data given during wht wizard post.
400      * @param additonalParams anything that the underlying wizard needs. The
401      *                        values are set by the Application Controller.
402      * @return Object: whatever object the wizard returns after processing.
403      * @throws WizardException upon error.
404      */
405     public Object processFinish(WizardPage src, Serializable data, Map additonalParams) throws WizardException {
406         finished = true;
407         return super.processFinish(src, data, additonalParams);
408     }
409 
410     /***
411      * {@inheritDoc}
412      *
413      * @return WizardPage instance.
414      * @throws WizardException upon error.
415      */
416     public WizardPage previous() throws WizardException {
417         finished = false;
418         return super.previous();
419     }
420 
421     /***
422      * Override of toString().
423      *
424      * @return Wizard Title
425      */
426     public String toString() {
427         return getTitle() + " Sequential Wizard";
428     }
429 
430 }