View Javadoc

1   package com.sri.emo.wizard.branch;
2   
3   import com.sri.emo.wizard.AbstractWizard;
4   import com.sri.emo.wizard.WizardMonitor;
5   import com.sri.emo.wizard.WizardPage;
6   import com.sri.emo.wizard.WizardException;
7   import java.util.Map;
8   import java.util.HashMap;
9   import java.io.Serializable;
10  import java.util.Stack;
11  import java.util.Arrays;
12  import java.util.List;
13  import com.sri.emo.wizard.defaults.SequentialWizard;
14  import java.util.Iterator;
15  import com.sri.emo.wizard.creation.StringUtil;
16  import java.util.ArrayList;
17  
18  /***
19   * <p>Title: </p>
20   *
21   * <p>Description: </p>
22   *
23   * <p>Copyright: Copyright (c) 2003</p>
24   *
25   * <p>Company: </p>
26   *
27   * @author not attributable
28   * @version 1.0
29   */
30  public class BranchingWizard extends AbstractWizard {
31  
32      /***
33       * An array of steps.  Since it's sequential, wizSteps[0] == first
34       * step, wizSteps[1] == second step and so on.
35       */
36      final private BranchNode[] wizSteps;
37  //    final private BranchNode currentStep;
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 BranchingWizard(final WizardMonitor wizMonitor, final BranchNode[] steps) {
54          super(wizMonitor);
55          wizSteps = steps;
56  //        currentStep = steps;
57      }
58  
59      /***
60       * Retrieve the initial page for the wizard.
61       *
62       * @return WizardPage instance.
63       */
64      protected WizardPage getInitialPage() {
65          return wizSteps[0].getPage();
66      }
67  
68      /***
69       * Retrieve the index of the current wizard.  First page is page 1,
70       * Second Page is page 2, etc.
71       *
72       * @return int
73       */
74      public int getCurrentPageIndex() {
75          return (findWizardPageIndex(getCurrentPage()) + 1);
76      }
77  
78      /***
79       * Retrieve the total number of pages in this sequential wizard.
80       *
81       * @return int the number of pages.
82       */
83  //    public int getTotalPages() {
84  //        return wizSteps.length;
85  //    }
86  
87      /***
88       * {@inheritDoc}
89       * <p>The SequentialWizard implementation allows for
90       * the browser back button
91       * to be pressed and still achieve status quo.  What happens is that
92       * if we detect that the browser back button was ealier pressed
93       * (we can tell because the src page parameter is already on
94       * the backtrace stack), then we rollback to the page previous to the
95       * src page and re-execute.</p>
96       *
97       * @param src     the wizard page source
98       * @param newData the data for the previous page.
99       * @return the next wizard page
100      * @throws WizardException                upon error
101      * @throws ArrayIndexOutOfBoundsException if next was clicked and
102      *                                        there should be no next button (ie last page)
103      */
104     public WizardPage next(WizardPage src, Serializable newData,
105             Serializable allData) throws WizardException {
106         finished = false;
107         System.out.println("BranchingWizard.next("+newData+")");
108 
109         getMonitor().onForward(src);
110         src.setData(newData);
111 
112         int val = findWizardPageIndex(src);
113         if (isNeedingRollback(src)) {
114             rollbackBacktrace(src);
115         }
116 
117         //Checks against illegal forward jumps.
118         checkAgainstIllegalJump(val);
119 
120         //
121         //Allow for the page to validate itself first.  If there's problems there
122         //don't even try continuing.
123         //
124         if (src.getPageErrors() != null && src.getPageErrors().size() > 0) {
125             return wizSteps[val].getPage();
126         }
127 
128         //Call template method and see if we can continue.
129         BranchNode nextNode = getTransition(wizSteps[val], allData);
130 
131 //        if (!onNextPage(wizSteps[val], wizSteps[val + 1], newData)) {
132         if (!onNextPage(wizSteps[val].getPage(), nextNode.getPage(), newData)) {
133             //Rollback
134             return wizSteps[val].getPage();
135         } else {
136             super.getBackTrace().push(src);
137 //            val++;
138 //            getMonitor().onEnterPage(wizSteps[val].getPage());
139 //            setCurrentPage(wizSteps[val].getPage());
140 //
141 //            return wizSteps[val].getPage();
142             getMonitor().onEnterPage(nextNode.getPage());
143             setCurrentPage(nextNode.getPage());
144 
145             return nextNode.getPage();
146 
147         }
148     }
149 
150 
151     /***
152      * {@inheritDoc}
153      * <p>The SequentialWizard implementation allows for
154      * the browser back button
155      * to be pressed and still achieve status quo.  What happens is that
156      * if we detect that the browser back button was ealier pressed
157      * (we can tell because the src page parameter is already on
158      * the backtrace stack), then we rollback to the page previous to the
159      * src page and re-execute.</p>
160      *
161      * @param src     the wizard page source
162      * @param newData the data for the previous page.
163      * @return the next wizard page
164      * @throws WizardException                upon error
165      * @throws ArrayIndexOutOfBoundsException if next was clicked and
166      *                                        there should be no next button (ie last page)
167      */
168     public WizardPage next(WizardPage src, Serializable newData) throws WizardException {
169         return next(src, newData, null);
170     }
171 
172     /***
173      * getTransition
174      *
175      * @param branchNode BranchNode
176      * @param newData Serializable
177      * @return BranchNode
178      */
179     private BranchNode getTransition(BranchNode branchNode,
180                                      Serializable newData) {
181         System.out.println("BranchingWizard.getTransition()");
182         if(newData == null){
183             System.out.println("data is null so returning default page");
184             return branchNode.getDefault();
185         }
186         if(newData instanceof String){
187             System.out.println("data is a string so returning default page");
188             return branchNode.getDefault();
189         }
190         if(newData instanceof List){
191             System.out.println("getTransition() LIST: " + StringUtil.toString((List)newData));
192             return branchNode.getDefault();
193         }
194         System.out.println("newData is of type " + newData.getClass());
195         Map map = (Map) newData;
196         String key = branchNode.getTransitionKey();
197         System.out.println("BranchingWizard.getTransition(), map = " + map);
198         System.out.println("transition key = " + key);
199         System.out.println("transition to = " + (String) map.get(key));
200         System.out.println("next node = " + branchNode.getNext((String) map.get(key)));
201         return branchNode.getNext((String) map.get(key));
202 
203     }
204 
205     /***
206      * Forgivingly converts page id to an internal integer.  Allowed parameters
207      * can be either Strings with integers or java.lang.Integer object.
208      *
209      * @param pageId Serializable the requested page id.
210      * @return int the converted value.
211      */
212     protected Integer convertKeyToPageId(Serializable pageId) {
213         assert pageId != null;
214 
215         if (pageId instanceof String) {
216             return new Integer((String) pageId);
217         } else if (pageId instanceof Integer) {
218             return ((Integer) pageId);
219         } else {
220             throw new IllegalArgumentException(
221                     "Page id must be some sort of integer, either in java.lang.Integer or java.lang.String form");
222         }
223     }
224 
225     /***
226      * Allows programmatic rewinding to a previous page.
227      *
228      * @param pageId Serializable the page key.
229      * @return WizardPage the resulting page.
230      */
231     public WizardPage backupToPage(Serializable pageKey) throws ArrayIndexOutOfBoundsException,
232             IllegalArgumentException {
233         assert pageKey != null;
234         System.out.println("BranchingWizard.backupToPage("+pageKey+")");
235         Integer pageId = convertKeyToPageId(pageKey);
236 //        assert pageId.intValue() > 0;
237 
238         WizardPage requestedPage = null;
239         for (int i = 0; i < wizSteps.length; i++) {
240             if (wizSteps[i].getPage().getId().equals(pageId)) {
241                 requestedPage = wizSteps[i].getPage();
242                 break;
243             }
244         }
245 
246         if (requestedPage == null) {
247             throw new IllegalArgumentException("Could not find page with id: " + pageKey.toString());
248         }
249 
250         rollbackBacktrace(requestedPage);
251         setCurrentPage(requestedPage);
252         getMonitor().onBack(requestedPage);
253 
254         getMonitor().onEnterPage(requestedPage);
255         return requestedPage;
256     }
257 
258 
259     /***
260      * Template method that gets called after the next page has been determined
261      * in the sequence.
262      *
263      * @param previousPage     The previousPage that was displayted
264      * @param nextPage         WizardPage the next page that is about to be displayed.
265      * @param previousPageData Serializable the previously entered data from the
266      *                         last page where the user clicked 'next'.
267      * @return boolean true if you wish for the user to continue.  False if
268      *         the page data needs to be vetoed.
269      * @throws WizardException upon error.
270      */
271     protected boolean onNextPage(final WizardPage previousPage,
272                                  final WizardPage nextPage,
273                                  final Serializable previousPageData) throws WizardException {
274 
275         return (previousPage.getPageErrors() == null);
276     }
277 
278     /***
279      * Checks that there were no illegally jumping forward moves
280      * caused by parameter manipulation.
281      *
282      * @param src WizardPage the wizard page we're checking against.
283      * @throws IllegalStateException if the check fails.
284      */
285     protected void checkAgainstIllegalJump(int pageIndex) {
286         Stack backtrace = getBackTrace();
287         if (backtrace.size() == 0) {
288             if (pageIndex == 0) {
289                 return;
290             } else {
291 //                throw new IllegalStateException("Illegal Page Order");
292             }
293         } else {
294             WizardPage previous = (WizardPage) backtrace.peek();
295             int previousIndex = findWizardPageIndex(previous);
296             int current = pageIndex;
297 //            System.out.println("checkAgainstIllegalJump("+pageIndex+")");
298 //            System.out.println("previous index = " + previousIndex);
299 //            System.out.println("current = " + current);
300 //            System.out.println("PREVIOUS: " + wizSteps[previousIndex]);
301 //            System.out.println("CURRENT: " + wizSteps[current]);
302 
303             if (!wizSteps[previousIndex].containsTransitionTo(wizSteps[current])) {
304 //                throw new IllegalStateException("Illegal Page Order");
305             }
306         }
307     }
308 
309     /***
310      * {@inheritDoc}
311      *
312      * @param s Serializable
313      * @return WizardPage
314      */
315     public WizardPage getPageById(Serializable s) {
316         for (int i = 0; i < wizSteps.length; i++) {
317             WizardPage onePage = wizSteps[i].getPage();
318 //            System.out.println("checking " + s + " against " + onePage.getId());
319             if (onePage.getId().equals(s)) {
320                 return onePage;
321             }
322         }
323 
324         throw new IllegalArgumentException("Key: " + s.toString()
325                 + " does not exist for this wizard");
326     }
327 
328     /***
329      * Retrieve all the steps for the wizard.
330      *
331      * @return WizardPage[]
332      */
333     protected BranchNode[] getAllSteps() {
334         return wizSteps;
335     }
336 
337     /***
338      * Checks the backtrace for presence of the given wizard page.
339      *
340      * @param src WizardPage the source wizard page.
341      * @return boolean true.
342      */
343     protected boolean isNeedingRollback(WizardPage src) {
344         Stack backtrace = getBackTrace();
345         boolean foundInStack = false;
346         for (int i = 0; i < backtrace.size(); i++) {
347             WizardPage onePage = (WizardPage) backtrace.get(i);
348             if (src.equals(onePage)) {
349                 foundInStack = true;
350                 break;
351             }
352         }
353 
354         return foundInStack;
355     }
356 
357 
358     /***
359      * Rolls back the backtrace stack if the src page is already
360      * on the stack, indicating that the browser pushed the back button.
361      *
362      * @param src WizardPage the source of the 'next' event, so we rollback
363      *            so that it is no longer on the backtrace.
364      */
365     protected void rollbackBacktrace(final WizardPage src) {
366         Stack backtrace = getBackTrace();
367 
368         while (!backtrace.empty()) {
369             WizardPage onePage = (WizardPage) backtrace.pop();
370             if (src.equals(onePage)) {
371                 break;
372             }
373         }
374     }
375 
376 
377     /***
378      * Find the wizard page index so we can define what the next page is.
379      *
380      * @param toFind the wizard page to find.
381      * @return integer, the index inside wizSteps
382      * @throws IllegalStateException if we're unable to find the page.
383      */
384     protected int findWizardPageIndex(final WizardPage toFind) {
385 
386         for (int i = 0; i < wizSteps.length; i++) {
387             if (wizSteps[i].getPage() == toFind) {
388                 return i;
389             }
390         }
391         throw new IllegalStateException("Unable to find Wizard Page");
392     }
393 
394 
395     /***
396      * Retrieve all the data for the wizard keyed by page id.
397      *
398      * @return Map of WizardPage objects keyed by id.
399      * @throws WizardException upon error
400      */
401     public Map getAllData() throws WizardException {
402         Map returnValue = new HashMap(wizSteps.length);
403 
404         for (int i = 0; i < wizSteps.length; i++) {
405             WizardPage oneStep = wizSteps[i].getPage();
406             returnValue.put(oneStep.getId(), oneStep.getData());
407         }
408 
409         return returnValue;
410     }
411 
412     /***
413      * {@inheritDoc}
414      *
415      * @return List
416      * @throws WizardException upon retrieval order.
417      */
418     public List getStepHistory() throws WizardException {
419         //Typically
420         int currentIndex = getCurrentPageIndex() - 1;
421 
422         //If we are finished
423         if (finished) {
424             //Include the last page that was not normally
425             //processed by next.
426             currentIndex++;
427         }
428 
429         //Create a new array and copy the appropriate elements
430         //Return the array as a list.
431         WizardPage history[] = new WizardPage[currentIndex];
432         for (int i = 0; i < currentIndex; i++) {
433             history[i] = wizSteps[i].getPage();
434         }
435         return Arrays.asList(history);
436     }
437 
438     /***
439      * Will return a history of steps without accounting for any of the branching
440      * @return List
441      * @throws WizardException
442      */
443     public List getMainStepHistory() throws WizardException {
444         //Typically
445         int currentIndex = getCurrentPageIndex() - 1;
446 
447         //If we are finished
448         if (finished) {
449             //Include the last page that was not normally
450             //processed by next.
451             currentIndex++;
452         }
453 
454         //Create a new array and copy the appropriate elements
455         //Return the array as a list.
456 //    WizardPage history[] = new WizardPage[currentIndex];
457         ArrayList list = new ArrayList(currentIndex);
458         for (int i = 0; i < currentIndex; i++) {
459             if (includeInMainStep(wizSteps[i])) {
460                 list.add(wizSteps[i].getPage());
461             }
462         }
463         return list;
464     }
465 
466     protected boolean includeInMainStep(BranchNode node){
467         return true;
468     }
469 
470 
471     /***
472      * Override of Object.equals to check field by field.
473      * {@inheritDoc}
474      *
475      * @param parm1 the object SequentialWizard
476      * @return true if the fields are equal.
477      */
478     public boolean equals(final Object parm1) {
479         if (super.equals(parm1)) {
480             BranchingWizard other = (BranchingWizard) parm1;
481             boolean returnValue = true;
482             returnValue &= (wizSteps.length == other.wizSteps.length);
483             if (returnValue) {
484                 for (int i = 0; i < wizSteps.length; i++) {
485                     returnValue &= wizSteps[i].equals(other.wizSteps[i]);
486                 }
487             }
488             return returnValue;
489         } else {
490             return false;
491         }
492     }
493 
494     /***
495      * Returns a hash code value for the object.
496      *
497      * @return a hash code value for this object.
498      */
499     public int hashCode() {
500         return super.hashCode();
501     }
502 
503     /***
504      * Processes the final finish and returns some sort of data that can be
505      * whatever is desired.
506      *
507      * @param src             WizardPage the source of the event.
508      * @param data            Serializable the data given during wht wizard post.
509      * @param additonalParams anything that the underlying wizard needs. The
510      *                        values are set by the Application Controller.
511      * @return Object: whatever object the wizard returns after processing.
512      * @throws WizardException upon error.
513      */
514     public Object processFinish(WizardPage src, Serializable data, Map additonalParams) throws WizardException {
515         finished = true;
516         return super.processFinish(src, data, additonalParams);
517     }
518 
519     /***
520      * {@inheritDoc}
521      *
522      * @return WizardPage instance.
523      * @throws WizardException upon error.
524      */
525     public WizardPage previous() throws WizardException {
526         finished = false;
527         return super.previous();
528     }
529 
530     /***
531      * Override of toString().
532      *
533      * @return Wizard Title
534      */
535     public String toString() {
536         return getTitle() + " Branching Wizard";
537     }
538 
539 }