1
2
3
4
5
6
7
8
9
10
11
12 package sk.uniba.euromath.editor.wizards;
13 import java.util.ArrayList;
14 import java.util.IdentityHashMap;
15 import java.util.List;
16 import java.util.ListIterator;
17 import java.util.NoSuchElementException;
18 import org.eclipse.core.runtime.IStatus;
19 import org.eclipse.swt.graphics.RGB;
20 import sk.uniba.euromath.EuroMath;
21 /***
22 * <p>
23 * Multi-wizard switcher. Each wizard it encapsulates must comply to these
24 * rules:
25 * </p>
26 * <ul>
27 * <li><code>getPreviousPage(getNextPage(page)) == page</code> for any page
28 * the wizard ever returns, except the last page,</li>
29 * <li><code>getPreviousPage(getStartingPage()) == null</code></li>
30 * <li><code>getStartingPage()</code> must consistently return same instance
31 * of page object</li>
32 * <li><code>createPageControls()</code> is not called.</li>
33 * <li>it must be able to be cancelled anytime</li>
34 * </ul>
35 * <p>
36 * This wizard guarantees that
37 * </p>
38 * <ul>
39 * <li><code>getNextPage()</code> and <code>getPreviousPage()</code> of
40 * enclosed wizard will be called only when <code>this.getNextPage()</code>
41 * (or <code>this.getPreviousPage()</code> respectively) is called.</li>
42 * <li><code>performFinish()</code> and <code>performCancel()</code> are
43 * called only when this wizard's <code>performFinish()</code> (or
44 * <code>performCancel()</code> respectively) is called. Thus, if next wizard
45 * (in its creation time) depends on results of previous wizard, you can't
46 * compute those results in the <code>performFinish()</code> method.</li>
47 * </ul>
48 * <p>
49 * This wizard does not support any dialog settings - setting them has no
50 * effect.
51 * </p>
52 * @author Martin Vysny
53 */
54 public class MultiWizard implements IWizard {
55 /***
56 * Creates instance of wizard switcher.
57 * @param provider provides switcher with wizard instances.
58 */
59 public MultiWizard(IMultiWizardProvider provider) {
60 super();
61 this.provider = provider;
62 provider.setWizard(this);
63 if (provider.hasPrevious())
64 throw new IllegalArgumentException(
65 "Provider must be able to offer first wizard initially.");
66 if (provider.current() == null)
67 throw new IllegalArgumentException(
68 "Provider must be able to offer at least one wizard.");
69 }
70 /***
71 * Provides switcher with wizard instances.
72 */
73 protected final IMultiWizardProvider provider;
74 /***
75 * Contains list of wizards. Last wizard is always the active wizard.
76 */
77 protected final List<IWizard> wizards = new ArrayList<IWizard>();
78 /***
79 * Contains pages created by the wizards.
80 */
81 protected final IdentityHashMap<IWizard, List<BaseWizardPage>> pages = new IdentityHashMap<IWizard, List<BaseWizardPage>>();
82 /***
83 * Returns active wizard - the last item of <code>wizards</code> array.
84 * @return active wizard.
85 */
86 protected final IWizard getActiveWizard() {
87 if (wizards.size() == 0) {
88
89 assert !provider.hasPrevious();
90 final IWizard wizard = provider.current();
91 wizards.add(wizard);
92
93 List<BaseWizardPage> wizardPages = new ArrayList<BaseWizardPage>();
94 final BaseWizardPage firstPage = wizard.current();
95 wizardPages.add(firstPage);
96 pages.put(wizard, wizardPages);
97 }
98 return wizards.get(wizards.size() - 1);
99 }
100
101
102
103
104 public boolean canFinish() {
105 return getActiveWizard().canFinish() && (!provider.hasNext());
106 }
107
108
109
110
111 public void dispose() {
112
113 provider.dispose();
114 for (IWizard w : wizards) {
115 w.dispose();
116 }
117 wizards.clear();
118 pages.clear();
119 }
120 /***
121 * Finds given page and returns its location in the <code>wizards</code>
122 * and <code>pages</code> properties.
123 * @param page page to find
124 * @return the page location, never <code>null</code>.
125 */
126 protected PageLocator getPage(BaseWizardPage page) {
127
128 for (ListIterator<IWizard> i = wizards.listIterator(wizards.size()); i
129 .hasPrevious();) {
130 IWizard wizard = i.previous();
131 List<BaseWizardPage> wizardPages = pages.get(wizard);
132 int index = wizardPages.lastIndexOf(page);
133 if (index >= 0) {
134 return new PageLocator(wizard, i.nextIndex(), index);
135 }
136 }
137 throw new IllegalArgumentException("Page " + page + " cannot be found");
138 }
139 /***
140 * Locates page.
141 * @author Martin Vysny
142 */
143 protected class PageLocator implements Comparable<PageLocator> {
144 /***
145 * Wizard containing requested page.
146 */
147 protected final IWizard wizard;
148 /***
149 * Index of wizard, containing requested page.
150 */
151 protected final int wizardIndex;
152 /***
153 * Index of requested page.
154 */
155 protected final int pageIndex;
156 /***
157 * Constructor.
158 * @param wizard the wizard.
159 * @param wizardIndex index of the wizard in the wizards list.
160 * @param pageIndex index of requested page.
161 */
162 protected PageLocator(IWizard wizard, int wizardIndex, int pageIndex) {
163 super();
164 this.wizard = wizard;
165 this.wizardIndex = wizardIndex;
166 this.pageIndex = pageIndex;
167 }
168 /***
169 * Returns the page instance.
170 * @return the page instance.
171 */
172 protected BaseWizardPage getPage() {
173 return pages.get(wizard).get(pageIndex);
174 }
175
176
177
178
179 public int compareTo(PageLocator o) {
180 if ((o == this) || equals(o))
181 return 0;
182 if (wizardIndex < o.wizardIndex)
183 return -1;
184 if (wizardIndex > o.wizardIndex)
185 return 1;
186 if (pageIndex < o.pageIndex)
187 return -1;
188 if (pageIndex > o.pageIndex)
189 return 1;
190
191 throw new AssertionError();
192 }
193
194
195
196
197 @Override
198 public boolean equals(Object obj) {
199 if (obj == null)
200 return false;
201 if (!(obj instanceof PageLocator))
202 return false;
203 PageLocator other = (PageLocator) obj;
204 if ((wizardIndex != other.wizardIndex)
205 || (pageIndex != other.pageIndex))
206 return false;
207
208 assert other.wizard == wizard;
209 return true;
210 }
211
212
213
214
215 @Override
216 public String toString() {
217 return "Wizard " + wizardIndex + " page " + pageIndex;
218 }
219 /***
220 * Returns locator for previous page.
221 * @return locator for previous page, or <code>null</code> if this
222 * locator is first.
223 */
224 protected PageLocator getPrevious() {
225 if (pageIndex > 0)
226 return new PageLocator(wizard, wizardIndex, pageIndex - 1);
227 if (wizardIndex == 0)
228 return null;
229 IWizard prevWizard = wizards.get(wizardIndex - 1);
230 return new PageLocator(prevWizard, wizardIndex - 1, pages.get(
231 prevWizard).size() - 1);
232 }
233 /***
234 * Returns locator for next page.
235 * @return locator for next page, or <code>null</code> if next page is
236 * not available/created/registered.
237 */
238 protected PageLocator getNext() {
239 List<BaseWizardPage> pageDelegates = pages.get(wizard);
240 if (pageDelegates.size() - 1 > pageIndex)
241 return new PageLocator(wizard, wizardIndex, pageIndex + 1);
242 if (wizards.size() - 1 > wizardIndex)
243 return new PageLocator(wizards.get(wizardIndex + 1),
244 wizardIndex + 1, 0);
245 return null;
246 }
247 }
248 /***
249 * Returns active (last) page.
250 * @return locator of active page.
251 */
252 protected PageLocator getActivePage() {
253 IWizard wizard = getActiveWizard();
254
255
256
257 return new PageLocator(wizard, wizards.size() - 1, pages.get(wizard)
258 .size() - 1);
259 }
260 /***
261 * Deletes all pages after given page. Disposes all unused wizards
262 * automatically.
263 * @param locator the locator of the page.
264 * @param including if <code>true</code> then page itself will be deleted.
265 * In this case the locator must not denote starting page.
266 */
267 protected void deletePagesAfter(PageLocator locator, boolean including) {
268 if (including && (locator.getPage() == getStartingPage()))
269 throw new IllegalArgumentException(
270 "Starting page cannot be deleted.");
271 while (true) {
272 PageLocator actPage = getActivePage();
273 final boolean delete = including ? locator.compareTo(actPage) <= 0
274 : locator.compareTo(actPage) < 0;
275 if (!delete) {
276
277 return;
278 }
279 if (actPage.pageIndex != 0) {
280
281 BaseWizardPage page;
282 try {
283 page = actPage.wizard.previous();
284 } catch (NoSuchElementException ex) {
285
286 throw new IllegalStateException(
287 "Wizard violates rules: it modifies shown pages.");
288 }
289
290 if (page != actPage.getPrevious().getPage())
291 throw new IllegalStateException(
292 "Wizard violates rules: it modifies shown pages");
293
294 pages.get(actPage.wizard).remove(actPage.pageIndex);
295 } else {
296
297 pages.remove(actPage.wizard);
298 wizards.remove(wizards.size() - 1);
299 actPage.wizard.dispose();
300 IWizard prevWizard = provider.previous();
301
302 actPage = getActivePage();
303 if (prevWizard != actPage.wizard)
304 throw new IllegalStateException(
305 "Wizard provider violates rules: it modifies already returned wizards");
306 }
307 }
308 }
309
310
311
312
313 public boolean hasNext() {
314 final PageLocator currentLocator = getActivePage();
315
316
317 if (currentLocator.getPage().hasErrors()) {
318
319 return false;
320 }
321 if (!currentLocator.wizard.canFinish()) {
322
323
324 return true;
325 }
326
327 return provider.hasNext();
328 }
329
330
331
332
333 public BaseWizardPage current() {
334 return getActivePage().getPage();
335 }
336
337
338
339
340 public String getName() {
341 if (provider.getName() != null)
342 return provider.getName();
343 return getActivePage().wizard.getName();
344 }
345
346
347
348
349 public BaseWizardPage next() throws ProviderException {
350 if (!hasNext())
351 throw new NoSuchElementException();
352 final PageLocator currentLocator = getActivePage();
353
354 IWizard nextWizard = currentLocator.wizard;
355 final BaseWizardPage nextPage;
356 if (!nextWizard.hasNext()) {
357
358
359 if (!currentLocator.wizard.canFinish() || !provider.hasNext()) {
360
361
362 throw new AssertionError(
363 "hasNext() should have checked this possibility..");
364 }
365
366
367 nextWizard = provider.next();
368 if (nextWizard == null)
369 throw new NullPointerException("provider returned null wizard");
370 wizards.add(nextWizard);
371 List<BaseWizardPage> nextWizardPages = new ArrayList<BaseWizardPage>();
372 pages.put(nextWizard, nextWizardPages);
373 nextPage = nextWizard.current();
374 } else {
375 nextPage = nextWizard.next();
376 }
377
378 pages.get(nextWizard).add(nextPage);
379 return nextPage;
380 }
381
382
383
384
385 public boolean hasPrevious() {
386 return getStartingPage() != getActivePage().getPage();
387 }
388
389
390
391
392 public BaseWizardPage previous() {
393 if (!hasPrevious())
394 throw new NoSuchElementException();
395 final PageLocator currentLocator = getActivePage();
396 deletePagesAfter(currentLocator, true);
397 return getActivePage().getPage();
398 }
399 /***
400 * Retrieves the first page of the first wizard.
401 * @return first page instance.
402 */
403 final protected BaseWizardPage getStartingPage() {
404
405 getActiveWizard();
406 final IWizard first = wizards.get(0);
407 final BaseWizardPage firstPage = pages.get(first).get(0);
408 return firstPage;
409 }
410
411
412
413
414 public RGB getTitleBarColor() {
415 return getActiveWizard().getTitleBarColor();
416 }
417
418
419
420
421 public boolean performCancel() {
422
423
424 final ListIterator<IWizard> i = wizards.listIterator(wizards.size());
425
426 for (; i.hasPrevious(); ) {
427 final IWizard wizard = i.previous();
428
429 if (!wizard.performCancel()) {
430 EuroMath.log(IStatus.WARNING, 0, "Unexpected: Wizard " + wizard
431 + " is not cancellable.", null);
432 }
433 }
434 provider.performCancel();
435 return true;
436 }
437
438
439
440
441 public boolean performFinish() {
442
443 if (!canFinish())
444 return false;
445
446 for (IWizard wizard : wizards) {
447
448 if (!wizard.performFinish())
449 throw new RuntimeException("Unexpected: Wizard " + wizard
450 + " cannot finish");
451 }
452 provider.performFinish();
453 return true;
454 }
455 /***
456 * Returns the provider instance. The provider's <code>next()</code> and
457 * <code>previous()</code> methods must not be called.
458 * @return the provider instance.
459 */
460 public final IMultiWizardProvider getProvider() {
461 return provider;
462 }
463 }