1
2
3
4
5
6
7
8
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
114 checkAgainstIllegalJump(src);
115
116
117
118
119
120 if (src.getPageErrors() != null && src.getPageErrors().size() > 0) {
121 return wizSteps[val];
122 }
123
124
125
126 if (!onNextPage(wizSteps[val], wizSteps[val + 1], newData)) {
127
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
346 int currentIndex = getCurrentPageIndex() - 1;
347
348
349 if (finished) {
350
351
352 currentIndex++;
353 }
354
355
356
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 }