001: /**
002: * Wizard Framework
003: * Copyright 2004 - 2005 Andrew Pietsch
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *
019: * $Id: MultiPathModel.java,v 1.13 2005/05/22 07:13:31 pietschy Exp $
020: */package org.pietschy.wizard.models;
021:
022: import org.pietschy.wizard.AbstractWizardModel;
023: import org.pietschy.wizard.WizardStep;
024:
025: import java.util.*;
026: import java.beans.PropertyChangeListener;
027: import java.beans.PropertyChangeEvent;
028:
029: /**
030: * MultiPathModels are built from a joined set of {@link Path Paths} that each contain one or more
031: * {@link WizardStep WizardSteps}. Two types of {@link Path} are available, {@link SimplePath} and
032: * {@link BranchingPath}. The paths must be fully constructed before the model is instantiated.
033: * <pre>
034: * // Construct each of the paths involved in the wizard.
035: *BranchingPath firstPath = new BranchingPath();
036: *SimplePath optionalPath = new SimplePath();
037: *SimplePath lastPath = new SimplePath();
038: *
039: *firstPath.addStep(stepOne);
040: *firstPath.addStep(stepTwo);
041: *
042: *optionalPath.addStep(optionalStepOne);
043: *optionalPath.addStep(optionalStepTwo);
044: *optionalPath.addStep(optionalStepThree);
045: *
046: *lastPath.addStep(lastStep);
047: *
048: * // Now bind all the paths together, first the branching path then the optional path.
049: *
050: * // add the optional path and the condition that determines when it should be followed
051: *firstPath.addBranch(optionalPath, new Condition() {
052: * public boolean evaluate(WizardModel model) {
053: * return ((MyModel)model).includeOptional();
054: * }
055: *});
056: *
057: * // add the end path and the condition that determines when it should be followed
058: *firstPath.addBranch(lastPath, new Condition() {
059: * public boolean evaluate(WizardModel model) {
060: * return !((MyModel)model).includeOptional();
061: * }
062: *});
063: *
064: * // the optional path proceeds directly to the lastPath
065: *optionalPath.setNextPath(lastPath);
066: *
067: * // Now create the model and wizard.
068: *MultiPathModel model = new MultiPathModel(firstPath);
069: *Wizard wizard = new Wizard(model);
070: * </pre>
071: *
072: * During the initialization the wizard will scan all the paths to determine the ending path. The
073: * end path is an instance of {@link SimplePath} that is reachable from the
074: * {@link #getFirstPath firstPath} and for whom {@link SimplePath#getNextPath} returns null. If no
075: * matching path is found or more than one is found the model will throw an exception.
076: */
077: public class MultiPathModel extends AbstractWizardModel {
078: private Path firstPath;
079: private Path lastPath;
080: private Map pathMapping;
081:
082: private Stack history = new Stack();
083:
084: /**
085: * Creates a new MultiPathModel. The paths must be full constructed and
086: * linked before the this constructor is called.<p>
087: * During the initialization the wizard will scan all the paths to determine the ending path. The
088: * end path is an instance of {@link SimplePath} that is reachable from the
089: * {@link #getFirstPath firstPath} and for whom {@link SimplePath#getNextPath} returns null. If no
090: * matching path is found or more than one is found the model will throw an exception.
091: * @param firstPath the starting path of the model. The paths must be populated with their
092: * {@link WizardStep steps} and be linked before the this constructor is called.
093: */
094: public MultiPathModel(Path firstPath) {
095: this .firstPath = firstPath;
096:
097: PathMapVisitor visitor = new PathMapVisitor();
098: firstPath.acceptVisitor(visitor);
099: pathMapping = visitor.getMap();
100:
101: LastPathVisitor v = new LastPathVisitor();
102: firstPath.acceptVisitor(v);
103: lastPath = v.getPath();
104:
105: if (lastPath == null)
106: throw new IllegalStateException(
107: "Unable to locate last path");
108:
109: for (Iterator iter = pathMapping.keySet().iterator(); iter
110: .hasNext();) {
111: addCompleteListener((WizardStep) iter.next());
112: }
113: }
114:
115: public Path getFirstPath() {
116: return firstPath;
117: }
118:
119: public Path getLastPath() {
120: return lastPath;
121: }
122:
123: public void nextStep() {
124: WizardStep currentStep = getActiveStep();
125: Path currentPath = getPathForStep(currentStep);
126:
127: if (currentPath.isLastStep(currentStep)) {
128: Path nextPath = currentPath.getNextPath(this );
129: setActiveStep(nextPath.firstStep());
130: } else {
131: setActiveStep(currentPath.nextStep(currentStep));
132: }
133:
134: history.push(currentStep);
135: }
136:
137: public void previousStep() {
138: WizardStep step = (WizardStep) history.pop();
139: setActiveStep(step);
140: }
141:
142: public void lastStep() {
143: history.push(getActiveStep());
144: WizardStep lastStep = getLastPath().lastStep();
145: setActiveStep(lastStep);
146: }
147:
148: public void reset() {
149: history.clear();
150: WizardStep firstStep = firstPath.firstStep();
151: setActiveStep(firstStep);
152: history.push(firstStep);
153: }
154:
155: public boolean isLastStep(WizardStep step) {
156: Path path = getPathForStep(step);
157: return path.equals(getLastPath()) && path.isLastStep(step);
158: }
159:
160: public void refreshModelState() {
161: WizardStep activeStep = getActiveStep();
162: Path activePath = getPathForStep(activeStep);
163:
164: setNextAvailable(activeStep.isComplete()
165: && !isLastStep(activeStep));
166: setPreviousAvailable(!(activePath.equals(firstPath) && activePath
167: .isFirstStep(activeStep)));
168: setLastAvailable(allStepsComplete() && !isLastStep(activeStep));
169: setCancelAvailable(true);
170: }
171:
172: /**
173: * Returns true if all the steps in the wizard return <tt>true</tt> from
174: * {@link WizardStep#isComplete}. This is primarily used to determine if the last button
175: * can be enabled.
176: * @return <tt>true</tt> if all the steps in the wizard are complete, <tt>false</tt> otherwise.
177: */
178: public boolean allStepsComplete() {
179: for (Iterator iterator = stepIterator(); iterator.hasNext();) {
180: if (!((WizardStep) iterator.next()).isComplete())
181: return false;
182: }
183:
184: return true;
185: }
186:
187: public Iterator stepIterator() {
188: return pathMapping.keySet().iterator();
189: }
190:
191: protected Path getPathForStep(WizardStep step) {
192: return (Path) pathMapping.get(step);
193: }
194:
195: private class LastPathVisitor extends AbstractPathVisitor {
196: private Path last;
197:
198: public void visitPath(SimplePath p) {
199: if (enter(p)) {
200: if (p.getNextPath() == null) {
201: if (this .last != null)
202: throw new IllegalStateException(
203: "Two paths have empty values for nextPath");
204:
205: this .last = p;
206: } else {
207: p.visitNextPath(this );
208: }
209: }
210: }
211:
212: public void visitPath(BranchingPath path) {
213: if (enter(path))
214: path.visitBranches(this );
215: }
216:
217: public Path getPath() {
218: return last;
219: }
220: }
221:
222: private class PathMapVisitor extends AbstractPathVisitor {
223: private HashMap map = new HashMap();
224:
225: public PathMapVisitor() {
226: }
227:
228: public void visitPath(SimplePath path) {
229: if (enter(path)) {
230: populateMap(path);
231: path.visitNextPath(this );
232: }
233: }
234:
235: public void visitPath(BranchingPath path) {
236: if (enter(path)) {
237: populateMap(path);
238: path.visitBranches(this );
239: }
240: }
241:
242: private void populateMap(Path path) {
243: for (Iterator iter = path.getSteps().iterator(); iter
244: .hasNext();) {
245: WizardStep step = (WizardStep) iter.next();
246: map.put(step, path);
247: }
248: }
249:
250: public Map getMap() {
251: return map;
252: }
253: }
254:
255: }
|