001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.extensions.wizard; 018 019import java.util.ArrayDeque; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.List; 024 025import org.apache.wicket.util.io.IClusterable; 026 027 028/** 029 * Default implementation of {@link IWizardModel}, which models a semi-static wizard. This means 030 * that all steps should be known upfront, and added to the model on construction. Steps can be 031 * optional by using {@link ICondition}. The wizard is initialized with a wizard model through 032 * calling method {@link Wizard#init(IWizardModel)}. 033 * <p> 034 * Steps can be added to this model directly using either the {@link #add(IWizardStep) normal add 035 * method} or {@link #add(IWizardStep, ICondition) the conditional add method}. 036 * </p> 037 * 038 * <p> 039 * <a href="https://wizard-framework.dev.java.net/">Swing Wizard Framework</a> served as a valuable 040 * source of inspiration. 041 * </p> 042 * 043 * @author Eelco Hillenius 044 */ 045public class WizardModel extends AbstractWizardModel 046{ 047 /** 048 * Interface for conditional displaying of wizard steps. 049 */ 050 @FunctionalInterface 051 public interface ICondition extends IClusterable 052 { 053 /** 054 * Evaluates the current state and returns whether the step that is coupled to this 055 * condition is available. 056 * 057 * @return True if the step this condition is coupled to is available, false otherwise 058 */ 059 public boolean evaluate(); 060 } 061 062 /** 063 * Condition that always evaluates true. 064 */ 065 public static final ICondition TRUE = (() -> true); 066 067 private static final long serialVersionUID = 1L; 068 069 /** The currently active step. */ 070 private IWizardStep activeStep; 071 072 /** Conditions with steps. */ 073 private final List<ICondition> conditions = new ArrayList<>(); 074 075 /** State history. */ 076 private final ArrayDeque<IWizardStep> history = new ArrayDeque<>(); 077 078 /** The wizard steps. */ 079 private final List<IWizardStep> steps = new ArrayList<>(); 080 081 /** 082 * Construct. 083 */ 084 public WizardModel() 085 { 086 } 087 088 /** 089 * Adds the next step to the wizard. If the {@link WizardStep} implements {@link ICondition}, 090 * then this method is equivalent to calling {@link #add(IWizardStep, ICondition) add(step, 091 * (ICondition)step)}. 092 * 093 * @param step 094 * the step to added. 095 */ 096 public void add(final IWizardStep step) 097 { 098 if (step instanceof ICondition) 099 { 100 add(step, (ICondition)step); 101 } 102 else 103 { 104 add(step, TRUE); 105 } 106 } 107 108 /** 109 * Adds an optional step to the model. The step will only be displayed if the specified 110 * condition is met. 111 * 112 * @param step 113 * The step to add 114 * @param condition 115 * the {@link ICondition} under which it should be included in the wizard. 116 */ 117 public void add(final IWizardStep step, final ICondition condition) 118 { 119 steps.add(step); 120 conditions.add(condition); 121 } 122 123 /** 124 * Gets the current active step the wizard should display. 125 * 126 * @return the active step. 127 */ 128 @Override 129 public final IWizardStep getActiveStep() 130 { 131 return activeStep; 132 } 133 134 /** 135 * Checks if the last button should be enabled. 136 * 137 * @return <tt>true</tt> if the last button should be enabled, <tt>false</tt> otherwise. 138 * @see IWizardModel#isLastVisible 139 */ 140 @Override 141 public boolean isLastAvailable() 142 { 143 return allStepsComplete() && !isLastStep(activeStep); 144 } 145 146 /** 147 * @see org.apache.wicket.extensions.wizard.IWizardModel#isLastStep(org.apache.wicket.extensions.wizard.IWizardStep) 148 */ 149 @Override 150 public boolean isLastStep(final IWizardStep step) 151 { 152 return findLastStep().equals(step); 153 } 154 155 /** 156 * Checks if the next button should be enabled. 157 * 158 * @return <tt>true</tt> if the next button should be enabled, <tt>false</tt> otherwise. 159 */ 160 @Override 161 public boolean isNextAvailable() 162 { 163 return activeStep.isComplete() && !isLastStep(activeStep); 164 } 165 166 /** 167 * Checks if the previous button should be enabled. 168 * 169 * @return <tt>true</tt> if the previous button should be enabled, <tt>false</tt> otherwise. 170 */ 171 @Override 172 public boolean isPreviousAvailable() 173 { 174 return !history.isEmpty(); 175 } 176 177 /** 178 * @see org.apache.wicket.extensions.wizard.IWizardModel#last() 179 */ 180 @Override 181 public void last() 182 { 183 history.push(getActiveStep()); 184 IWizardStep lastStep = findLastStep(); 185 setActiveStep(lastStep); 186 } 187 188 /** 189 * @see org.apache.wicket.extensions.wizard.IWizardModel#next() 190 */ 191 @Override 192 public void next() 193 { 194 history.push(getActiveStep()); 195 IWizardStep step = findNextVisibleStep(); 196 setActiveStep(step); 197 } 198 199 /** 200 * @see org.apache.wicket.extensions.wizard.IWizardModel#previous() 201 */ 202 @Override 203 public void previous() 204 { 205 IWizardStep step = history.pop(); 206 setActiveStep(step); 207 } 208 209 /** 210 * @see org.apache.wicket.extensions.wizard.IWizardModel#reset() 211 */ 212 @Override 213 public void reset() 214 { 215 history.clear(); 216 activeStep = null; 217 218 for (IWizardStep step : steps) 219 { 220 step.init(this); 221 } 222 223 setActiveStep(findNextVisibleStep()); 224 } 225 226 /** 227 * Sets the active step. 228 * 229 * @param step 230 * the new active step step. 231 */ 232 public void setActiveStep(final IWizardStep step) 233 { 234 if ((activeStep != null) && (step != null) && activeStep.equals(step)) 235 { 236 return; 237 } 238 239 activeStep = step; 240 241 fireActiveStepChanged(step); 242 } 243 244 /** 245 * @see IWizardModel#stepIterator() 246 */ 247 @Override 248 public Iterator<IWizardStep> stepIterator() 249 { 250 List<IWizardStep> filtered = new ArrayList<>(); 251 252 for (int s = 0; s < steps.size(); s++) { 253 if (conditions.get(s).evaluate()) { 254 filtered.add(steps.get(s)); 255 } 256 } 257 258 return filtered.iterator(); 259 } 260 261 /** 262 * Returns true if all the steps in the wizard return <tt>true</tt> from 263 * {@link IWizardStep#isComplete}. This is primarily used to determine if the last button can be 264 * enabled. 265 * 266 * @return <tt>true</tt> if all the steps in the wizard are complete, <tt>false</tt> otherwise. 267 */ 268 protected final boolean allStepsComplete() 269 { 270 for (IWizardStep step : steps) 271 { 272 if (!step.isComplete()) 273 { 274 return false; 275 } 276 } 277 278 return true; 279 } 280 281 /** 282 * Finds the last step in this model. 283 * 284 * @return The last step 285 */ 286 protected final IWizardStep findLastStep() 287 { 288 for (int i = conditions.size() - 1; i >= 0; i--) 289 { 290 ICondition condition = conditions.get(i); 291 if (condition.evaluate()) 292 { 293 return steps.get(i); 294 } 295 } 296 297 throw new IllegalStateException("Wizard contains no visible steps"); 298 } 299 300 /** 301 * Finds the next visible step based on the active step. 302 * 303 * @return The next visible step based on the active step 304 */ 305 protected final IWizardStep findNextVisibleStep() 306 { 307 int startIndex = (activeStep == null) ? 0 : steps.indexOf(activeStep) + 1; 308 309 for (int i = startIndex; i < conditions.size(); i++) 310 { 311 ICondition condition = conditions.get(i); 312 if (condition.evaluate()) 313 { 314 return steps.get(i); 315 } 316 } 317 318 throw new IllegalStateException("Wizard contains no more visible steps"); 319 } 320 321 /** 322 * Gets conditions. 323 * 324 * @return unmodifiable list of conditions 325 */ 326 public List<ICondition> getConditions() 327 { 328 return Collections.unmodifiableList(conditions); 329 } 330 331}