2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

Coverage Report - org.apache.shale.dialog.basic.BasicDialogContext
 
Classes in this File Line Coverage Branch Coverage Complexity
BasicDialogContext
0%
0/267
0%
0/63
4.72
BasicDialogContext$TopState
0%
0/7
N/A
4.72
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to you under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 package org.apache.shale.dialog.basic;
 19  
 
 20  
 import java.io.IOException;
 21  
 import java.io.Serializable;
 22  
 import java.util.ArrayList;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 
 26  
 import javax.faces.FacesException;
 27  
 import javax.faces.application.ViewHandler;
 28  
 import javax.faces.component.UIViewRoot;
 29  
 import javax.faces.context.ExternalContext;
 30  
 import javax.faces.context.FacesContext;
 31  
 import javax.faces.el.MethodBinding;
 32  
 
 33  
 import org.apache.commons.logging.Log;
 34  
 import org.apache.commons.logging.LogFactory;
 35  
 import org.apache.shale.dialog.Constants;
 36  
 import org.apache.shale.dialog.DialogContext;
 37  
 import org.apache.shale.dialog.DialogContextListener;
 38  
 import org.apache.shale.dialog.DialogContextManager;
 39  
 import org.apache.shale.dialog.base.AbstractDialogContext;
 40  
 import org.apache.shale.dialog.basic.model.ActionState;
 41  
 import org.apache.shale.dialog.basic.model.Dialog;
 42  
 import org.apache.shale.dialog.basic.model.EndState;
 43  
 import org.apache.shale.dialog.basic.model.State;
 44  
 import org.apache.shale.dialog.basic.model.SubdialogState;
 45  
 import org.apache.shale.dialog.basic.model.Transition;
 46  
 import org.apache.shale.dialog.basic.model.ViewState;
 47  
 
 48  
 /**
 49  
  * <p>Implementation of {@link DialogContext} for integrating
 50  
  * basic dialog support into the Shale Dialog Manager.</p>
 51  
  *
 52  
  * <p><strong>IMPLEMENTATION NOTE</strong> - Takes on the responsibilities
 53  
  * of the <code>org.apache.shale.dialog.Status</code> implementation in the
 54  
  * original approach.</p>
 55  
  *
 56  
  * @since 1.0.4
 57  
  */
 58  
 final class BasicDialogContext extends AbstractDialogContext
 59  
   implements Serializable {
 60  
 
 61  
 
 62  
     // ------------------------------------------------------------ Constructors
 63  
 
 64  
 
 65  
     /**
 66  
      * Serial version UID.
 67  
      */
 68  
     private static final long serialVersionUID = 4858871161274193403L;
 69  
 
 70  
 
 71  
     /**
 72  
      * <p>Construct a new instance.</p>
 73  
      *
 74  
      * @param manager {@link DialogContextManager} instance that owns us
 75  
      * @param dialog Configured dialog definition we will be following
 76  
      * @param id Dialog identifier assigned to this instance
 77  
      * @param parentDialogId Dialog identifier of the parent DialogContext
 78  
      *  instance associated with this one (if any)
 79  
      */
 80  
     BasicDialogContext(DialogContextManager manager, Dialog dialog,
 81  0
                        String id, String parentDialogId) {
 82  
 
 83  0
         this.manager = manager;
 84  0
         this.dialog = dialog;
 85  0
         this.dialogName = dialog.getName();
 86  0
         this.id = id;
 87  0
         this.parentDialogId = parentDialogId;
 88  
 
 89  0
         if (log().isDebugEnabled()) {
 90  0
             log().debug("Constructor(id=" + id + ", name="
 91  
                       + dialogName + ")");
 92  
         }
 93  
 
 94  0
     }
 95  
 
 96  
 
 97  
     // ------------------------------------------------- DialogContext Variables
 98  
 
 99  
 
 100  
     /**
 101  
      * <p>Flag indicating that this {@link DialogContext} is currently active.</p>
 102  
      */
 103  0
     private boolean active = true;
 104  
 
 105  
 
 106  
     /**
 107  
      * <p>The {@link Dialog} that this instance is executing.  This value is
 108  
      * transient and may need to be regenerated.</p>
 109  
      */
 110  0
     private transient Dialog dialog = null;
 111  
 
 112  
 
 113  
     /**
 114  
      * <p>The name of the {@link Dialog} that this instance is executing.</p>
 115  
      */
 116  0
     private String dialogName = null;
 117  
 
 118  
 
 119  
     /**
 120  
      * <p><code>Map</code> of configured dialogs, keyed by logical name.  This value is
 121  
      * transient and may need to be regenerated.</p>
 122  
      */
 123  0
     private transient Map dialogs = null;
 124  
 
 125  
 
 126  
     /**
 127  
      * <p>Dialog identifier for this instance.</p>
 128  
      */
 129  0
     private String id = null;
 130  
 
 131  
 
 132  
     /**
 133  
      * <p>{@link DialogContextManager} instance that owns us.</p>
 134  
      */
 135  0
     private DialogContextManager manager = null;
 136  
 
 137  
 
 138  
     /**
 139  
      * <p>The <code>Log</code> instance for this dialog context.
 140  
      * This value is lazily created (or recreated) as necessary.</p>
 141  
      */
 142  0
     private transient Log log = null;
 143  
 
 144  
 
 145  
     /**
 146  
      * <p>Identifier of the parent {@link DialogContext} associated with
 147  
      * this {@link DialogContext}, if any.  If there is no such parent,
 148  
      * this value is set to <code>null</code>.</p>
 149  
      */
 150  0
     private String parentDialogId = null;
 151  
 
 152  
 
 153  
     /**
 154  
      * <p>The stack of currently stored Position instances.  If there
 155  
      * are no entries, we have completed the exit state and should deactivate
 156  
      * ourselves.</p>
 157  
      */
 158  0
     private List positions = new ArrayList();
 159  
 
 160  
 
 161  
     /**
 162  
      * <p>Flag indicating that execution has started for this dialog.</p>
 163  
      */
 164  0
     private boolean started = false;
 165  
 
 166  
 
 167  
     /**
 168  
      * <p>The strategy identifier for dealing with saving and restoring
 169  
      * state information across requests.  This value is lazily
 170  
      * instantiated, and can be recalculated on demand as needed.</p>
 171  
      */
 172  0
     private transient String strategy = null;
 173  
 
 174  
 
 175  
     /**
 176  
      * <p>An empty parameter list to pass to the action method called by
 177  
      * a method binding expression.</p>
 178  
      */
 179  0
     private static final Object[] ACTION_STATE_PARAMETERS = new Object[0];
 180  
 
 181  
 
 182  
     /**
 183  
      * <p>Parameter signature we create for method bindings used to execute
 184  
      * expressions specified by an {@link ActionState}.</p>
 185  
      */
 186  0
     private static final Class[] ACTION_STATE_SIGNATURE = new Class[0];
 187  
 
 188  
 
 189  
     /**
 190  
      * <p>Flag outcome value that signals a need to transition to the
 191  
      * start state for this dialog.</p>
 192  
      */
 193  
     private static final String START_OUTCOME =
 194  
             "org.apache.shale.dialog.basic.START_OUTCOME";
 195  
 
 196  
 
 197  
     // ------------------------------------------------ DialogContext Properties
 198  
 
 199  
 
 200  
     /** {@inheritDoc} */
 201  
     public boolean isActive() {
 202  
 
 203  0
         return this.active;
 204  
 
 205  
     }
 206  
 
 207  
 
 208  
     /** {@inheritDoc} */
 209  
     public Object getData() {
 210  
 
 211  0
         synchronized (positions) {
 212  0
             int index = positions.size() - 1;
 213  0
             if (index < 0) {
 214  0
                 return null;
 215  
             }
 216  0
             Position position = (Position) positions.get(index);
 217  0
             return position.getData();
 218  0
         }
 219  
 
 220  
     }
 221  
 
 222  
 
 223  
 
 224  
     /** {@inheritDoc} */
 225  
     public void setData(Object data) {
 226  
 
 227  0
         synchronized (positions) {
 228  0
             int index = positions.size() - 1;
 229  0
             if (index < 0) {
 230  0
                 throw new IllegalStateException("Cannot set data when no positions are stacked");
 231  
             }
 232  0
             Position position = (Position) positions.get(index);
 233  0
             Object old = position.getData();
 234  0
             if ((old != null) && (old instanceof DialogContextListener)) {
 235  0
                 removeDialogContextListener((DialogContextListener) old);
 236  
             }
 237  0
             position.setData(data);
 238  0
             if ((data != null) && (data instanceof DialogContextListener)) {
 239  0
                 addDialogContextListener((DialogContextListener) data);
 240  
             }
 241  0
         }
 242  
 
 243  0
     }
 244  
 
 245  
 
 246  
     /** {@inheritDoc} */
 247  
     public String getId() {
 248  
 
 249  0
         return this.id;
 250  
 
 251  
     }
 252  
 
 253  
 
 254  
     /** {@inheritDoc} */
 255  
     public String getName() {
 256  
 
 257  0
         Position position = peek();
 258  0
         if (position != null) {
 259  0
             return position.getDialog().getName();
 260  
         } else {
 261  0
             return null;
 262  
         }
 263  
 
 264  
     }
 265  
 
 266  
 
 267  
     /** {@inheritDoc} */
 268  
     public Object getOpaqueState() {
 269  
 
 270  0
         if ("top".equals(strategy())) {
 271  0
             if (log().isTraceEnabled()) {
 272  0
                 log().trace("getOpaqueState<top> returns " + new TopState(peek().getState().getName(), positions.size()));
 273  
             }
 274  0
             return new TopState(peek().getState().getName(), positions.size());
 275  0
         } else if ("stack".equals(strategy())) {
 276  0
             if (log().isTraceEnabled()) {
 277  0
                 log().trace("getOpaqueStrategy<stack> returns stack of " + positions.size());
 278  
             }
 279  0
             return positions;
 280  
         } else {
 281  0
             if (log().isTraceEnabled()) {
 282  0
                 log().trace("getOpaqueStrategy<none> returns nothing");
 283  
             }
 284  0
             return null;
 285  
         }
 286  
 
 287  
     }
 288  
 
 289  
 
 290  
     /** {@inheritDoc} */
 291  
     public void setOpaqueState(Object opaqueState) {
 292  
 
 293  0
         if ("top".equals(strategy())) {
 294  0
             TopState topState = (TopState) opaqueState;
 295  0
             if (log().isTraceEnabled()) {
 296  0
                 log().trace("setOpaqueState<top> restores " + topState);
 297  
             }
 298  0
             if (topState.stackDepth != positions.size()) {
 299  0
                 throw new IllegalStateException("Restored stack depth expects "
 300  
                         + positions.size() + " but is actually "
 301  
                         + topState.stackDepth);
 302  
             }
 303  0
             Position top = peek();
 304  0
             String oldStateName = top.getState().getName();
 305  0
             if (!oldStateName.equals(topState.stateName)) {
 306  0
                 fireOnExit(oldStateName);
 307  0
                 top.setState(top.getDialog().findState(topState.stateName));
 308  0
                 fireOnEntry(topState.stateName);
 309  
             }
 310  0
         } else if ("stack".equals(strategy())) {
 311  0
             if (log().isTraceEnabled()) {
 312  0
                 log().trace("setOpaqueState<stack> restores stack of " + ((List) opaqueState).size());
 313  
             }
 314  0
             List list = (List) opaqueState;
 315  0
             String oldStateName = peek().getState().getName();
 316  0
             String newStateName = ((Position) list.get(list.size() - 1)).getState().getName();
 317  0
             if (!oldStateName.equals(newStateName) || (list.size() != positions.size())) {
 318  0
                 fireOnExit(oldStateName);
 319  0
                 positions = list;
 320  0
                 fireOnEntry(newStateName);
 321  
             }
 322  0
         } else {
 323  0
             if (log().isTraceEnabled()) {
 324  0
                 log().trace("setOpaqueState<none> restores nothing");
 325  
             }
 326  
             ; // Do nothing
 327  
         }
 328  
 
 329  0
     }
 330  
 
 331  
 
 332  
     /** {@inheritDoc} */
 333  
     public DialogContext getParent() {
 334  
 
 335  0
         if (this.parentDialogId != null) {
 336  0
             DialogContext parent = manager.get(this.parentDialogId);
 337  0
             if (parent == null) {
 338  0
                 throw new IllegalStateException("Dialog instance '"
 339  
                         + parentDialogId + "' was associated with this instance '"
 340  
                         + getId() + "' but is no longer available");
 341  
             }
 342  0
             return parent;
 343  
         } else {
 344  0
             return null;
 345  
         }
 346  
 
 347  
     }
 348  
 
 349  
 
 350  
     // --------------------------------------------------- DialogContext Methods
 351  
 
 352  
 
 353  
     /** {@inheritDoc} */
 354  
     public void advance(FacesContext context, String outcome) {
 355  
 
 356  0
         if (!started) {
 357  0
             throw new IllegalStateException("Dialog instance '"
 358  
                     + getId() + "' for dialog name '"
 359  
                     + getName() + "' has not yet been started");
 360  
         }
 361  
 
 362  0
         if (log().isDebugEnabled()) {
 363  0
             log().debug("advance(id=" + getId() + ", name=" + getName()
 364  
                       + ", outcome=" + outcome + ")");
 365  
         }
 366  
 
 367  
         // If the incoming outcome is null, we want to stay in the same
 368  
         // (view) state *without* recreating it, which would destroy
 369  
         // any useful information that components might have stored
 370  0
         if (outcome == null) {
 371  0
             if (log().isTraceEnabled()) {
 372  0
                 log().trace("punt early since outcome is null");
 373  
             }
 374  0
             return;
 375  
         }
 376  
 
 377  
         // Perform an initial transition from the current state based on
 378  
         // the logical outcome received from the (current) view state
 379  0
         Position position = peek();
 380  0
         if (!START_OUTCOME.equals(outcome)) {
 381  0
             transition(position, outcome);
 382  
         }
 383  0
         State state = position.getState();
 384  0
         String viewId = null;
 385  0
         boolean redirect = false;
 386  
 
 387  
         // Advance through states until we again encounter the
 388  
         // need to render a view state
 389  
         while (true) {
 390  
 
 391  0
             if (state instanceof ActionState) {
 392  0
                 ActionState astate = (ActionState) state;
 393  0
                 if (log().isTraceEnabled()) {
 394  0
                     log().trace("-->ActionState(method=" + astate.getMethod() + ")");
 395  
                 }
 396  
                 try {
 397  0
                     MethodBinding mb = context.getApplication().
 398  
                       createMethodBinding(astate.getMethod(), ACTION_STATE_SIGNATURE);
 399  0
                     outcome = (String) mb.invoke(context, ACTION_STATE_PARAMETERS);
 400  0
                 } catch (Exception e) {
 401  0
                     fireOnException(e);
 402  0
                 }
 403  0
                 transition(position, outcome);
 404  0
                 state = position.getState();
 405  0
                 continue;
 406  0
             } else if (state instanceof EndState) {
 407  0
                 if (log().isTraceEnabled()) {
 408  0
                     log().trace("-->EndState()");
 409  
                 }
 410  0
                 pop();
 411  0
                 position = peek();
 412  0
                 if (position == null) {
 413  0
                     stop(context);
 414  
                 }
 415  0
                 viewId = ((EndState) state).getViewId();
 416  0
                 redirect = ((EndState) state).isRedirect();
 417  0
                 break;
 418  0
             } else if (state instanceof SubdialogState) {
 419  0
                 SubdialogState sstate = (SubdialogState) state;
 420  0
                 if (log().isTraceEnabled()) {
 421  0
                     log().trace("-->SubdialogState(dialogName="
 422  
                               + sstate.getDialogName() + ")");
 423  
                 }
 424  0
                 Dialog subdialog = (Dialog) dialogs(context).get(sstate.getDialogName());
 425  0
                 if (subdialog == null) {
 426  0
                     throw new IllegalStateException("Cannot find dialog definition '"
 427  
                       + sstate.getDialogName() + "'");
 428  
                 }
 429  0
                 start(subdialog);
 430  0
                 position = peek();
 431  0
                 state = position.getState();
 432  0
                 continue;
 433  0
             } else if (state instanceof ViewState) {
 434  0
                 viewId = ((ViewState) state).getViewId();
 435  0
                 redirect = ((ViewState) state).isRedirect();
 436  0
                 if (log().isTraceEnabled()) {
 437  0
                     log().trace("-->ViewState(viewId="
 438  
                               + ((ViewState) state).getViewId()
 439  
                               + ",redirect=" + ((ViewState) state).isRedirect()
 440  
                               + ")");
 441  0
                 }
 442  
                 break;
 443  
             } else {
 444  0
                 throw new IllegalStateException
 445  
                   ("State '" + state.getName()
 446  
                    + "' of dialog '" + position.getDialog().getName()
 447  
                    + "' is of unknown type '" + state.getClass().getName() + "'");
 448  
             }
 449  
         }
 450  
 
 451  
         // Navigate to the requested view identifier (if any)
 452  0
         if (viewId == null) {
 453  0
             return;
 454  
         }
 455  0
         if (log().isTraceEnabled()) {
 456  0
             log().trace("-->Navigate(viewId=" + viewId + ")");
 457  
         }
 458  0
         ViewHandler vh = context.getApplication().getViewHandler();
 459  0
         if (redirect) {
 460  0
             String actionURL = vh.getActionURL(context, viewId);
 461  0
             if (actionURL.indexOf('?') < 0) {
 462  0
                 actionURL += '?';
 463  0
             } else {
 464  0
                 actionURL += '&';
 465  
             }
 466  0
             actionURL += Constants.DIALOG_ID + "=" + this.id;
 467  
             try {
 468  0
                 ExternalContext econtext = context.getExternalContext();
 469  0
                 econtext.redirect(econtext.encodeActionURL(actionURL));
 470  0
                 context.responseComplete();
 471  0
             } catch (IOException e) {
 472  0
                 throw new FacesException("Cannot redirect to " + actionURL, e);
 473  0
             }
 474  0
         } else {
 475  0
             UIViewRoot view = vh.createView(context, viewId);
 476  0
             view.setViewId(viewId);
 477  0
             context.setViewRoot(view);
 478  0
             context.renderResponse();
 479  
         }
 480  
 
 481  0
     }
 482  
 
 483  
 
 484  
     /** {@inheritDoc} */
 485  
     public void start(FacesContext context) {
 486  
 
 487  0
         if (started) {
 488  0
             throw new IllegalStateException("Dialog instance '"
 489  
                     + getId() + "' for dialog name '"
 490  
                     + getName() + "' has already been started");
 491  
         }
 492  0
         started = true;
 493  
 
 494  0
         if (log().isDebugEnabled()) {
 495  0
             log().debug("start(id=" + getId() + ", name="
 496  
                       + getName() + ")");
 497  
         }
 498  
 
 499  
         // inform listeners we've been started
 500  0
         fireOnStart();
 501  
 
 502  
         // set the dialog in motion
 503  0
         start(dialog);
 504  
 
 505  
         // Advance the computation of our dialog until a view state
 506  
         // is encountered, then navigate to it
 507  0
         advance(context, START_OUTCOME);
 508  
 
 509  0
     }
 510  
 
 511  
 
 512  
     /** {@inheritDoc} */
 513  
     public void stop(FacesContext context) {
 514  
 
 515  0
         if (!started) {
 516  0
             throw new IllegalStateException("Dialog instance '"
 517  
                     + getId() + "' for dialog name '"
 518  
                     + getName() + "' has not yet been started");
 519  
         }
 520  0
         started = false;
 521  
 
 522  0
         if (log().isDebugEnabled()) {
 523  0
             log().debug("stop(id=" + getId() + ", name="
 524  
                       + getName() + ")");
 525  
         }
 526  
 
 527  0
         deactivate();
 528  0
         manager.remove(this);
 529  
 
 530  
         // inform listeners
 531  0
         fireOnStop();
 532  
 
 533  0
     }
 534  
 
 535  
 
 536  
     // ------------------------------------------------- Package Private Methods
 537  
 
 538  
 
 539  
     /**
 540  
      * <p>Mark this {@link DialogContext} as being deactivated.  This should only
 541  
      * be called by the <code>remove()</code> method on our associated
 542  
      * {@link DialogContextManager}, or the logic of our <code>stop()</code>
 543  
      * method.</p>
 544  
      */
 545  
     void deactivate() {
 546  
 
 547  0
         while (positions.size() > 0) {
 548  0
             pop();
 549  0
         }
 550  0
         this.active = false;
 551  
 
 552  0
     }
 553  
 
 554  
 
 555  
     // --------------------------------------------------------- Private Methods
 556  
 
 557  
 
 558  
     /**
 559  
      * <p>Return the {@link Dialog} instance for the dialog we are executing,
 560  
      * regenerating it if necessary first.</p>
 561  
      *
 562  
      * @param context FacesContext for the current request
 563  
      * @return The {@link Dialog} instance we are executing
 564  
      */
 565  
     private Dialog dialog(FacesContext context) {
 566  
 
 567  0
         if (this.dialog == null) {
 568  0
             this.dialog = (Dialog) dialogs(context).get(this.dialogName);
 569  
         }
 570  0
         return this.dialog;
 571  
 
 572  
     }
 573  
 
 574  
 
 575  
     /**
 576  
      * <p>Return a <code>Map</code> of the configured {@link Dialog}s, keyed
 577  
      * by logical dialog name.</p>
 578  
      *
 579  
      * @param context FacesContext for the current request
 580  
      * @return The map of available dialogs, keyed by dialog logical name
 581  
      *
 582  
      * @exception IllegalStateException if dialog configuration has not
 583  
      *  been completed
 584  
      */
 585  
     private Map dialogs(FacesContext context) {
 586  
 
 587  
         // Return the cached instance (if any)
 588  0
         if (this.dialogs != null) {
 589  0
             return this.dialogs;
 590  
         }
 591  
 
 592  
         // Return the previously configured application scope instance (if any)
 593  0
         this.dialogs = (Map)
 594  
           context.getExternalContext().getApplicationMap().get(Globals.DIALOGS);
 595  0
         if (this.dialogs != null) {
 596  0
             return this.dialogs;
 597  
         }
 598  
 
 599  
         // Throw an exception if dialog configuration resources have not
 600  
         // been processed yet
 601  0
         throw new IllegalStateException("Dialog configuration resources have not yet been processed");
 602  
 
 603  
     }
 604  
 
 605  
 
 606  
     /**
 607  
      * <p>Return the <code>Log</code> instance for this dialog context,
 608  
      * creating one if necessary.</p>
 609  
      */
 610  
     private Log log() {
 611  
 
 612  0
         if (log == null) {
 613  0
             log = LogFactory.getLog(BasicDialogContext.class);
 614  
         }
 615  0
         return log;
 616  
 
 617  
     }
 618  
 
 619  
 
 620  
     /**
 621  
      * <p>Return a <code>Position</code> representing the currently executing
 622  
      * dialog and state (if any); otherwise, return <code>null</code>.</p>
 623  
      *
 624  
      * @return Position representing the currently executing dialog and
 625  
      *         state, may be null
 626  
      */
 627  
     private Position peek() {
 628  
 
 629  0
         synchronized (positions) {
 630  0
             int index = positions.size() - 1;
 631  0
             if (index < 0) {
 632  0
                 return null;
 633  
             }
 634  0
             return (Position) positions.get(index);
 635  0
         }
 636  
 
 637  
     }
 638  
 
 639  
 
 640  
     /**
 641  
      * <p>Pop the currently executing <code>Position</code> and return the
 642  
      * previously executing <code>Position</code> (if any); otherwise,
 643  
      * return <code>null</code>.</p>
 644  
      *
 645  
      * @return Position representing the previously executing dialog and
 646  
      *         state (may be null), currently executing dialog Position
 647  
      *         is lost.
 648  
      */
 649  
     private Position pop() {
 650  
 
 651  0
         synchronized (positions) {
 652  0
             int index = positions.size() - 1;
 653  0
             if (index < 0) {
 654  0
                 throw new IllegalStateException("No position to be popped");
 655  
             }
 656  0
             Object data = ((Position) positions.get(index)).getData();
 657  0
             if ((data != null) && (data instanceof DialogContextListener)) {
 658  0
                 removeDialogContextListener((DialogContextListener) data);
 659  
             }
 660  0
             positions.remove(index);
 661  0
             if (index > 0) {
 662  0
                 return (Position) positions.get(index - 1);
 663  
             } else {
 664  0
                 return null;
 665  
             }
 666  0
         }
 667  
 
 668  
     }
 669  
 
 670  
 
 671  
     /**
 672  
      * <p>Push the specified <code>Position</code>, making it the currently
 673  
      * executing one.</p>
 674  
      *
 675  
      * @param position The new currently executing <code>Position</code>
 676  
      */
 677  
     private void push(Position position) {
 678  
 
 679  0
         if (position == null) {
 680  0
             throw new IllegalArgumentException();
 681  
         }
 682  0
         Object data = position.getData();
 683  0
         if ((data != null) && (data instanceof DialogContextListener)) {
 684  0
             addDialogContextListener((DialogContextListener) data);
 685  
         }
 686  0
         synchronized (positions) {
 687  0
             positions.add(position);
 688  0
         }
 689  
 
 690  0
     }
 691  
 
 692  
 
 693  
     /**
 694  
      * <p>Push a new {@link Position} instance representing the starting
 695  
      * {@link State} of the specified {@link Dialog}.</p>
 696  
      *
 697  
      * @param dialog {@link Dialog} instance to be started and pushed
 698  
      */
 699  
     private void start(Dialog dialog) {
 700  
 
 701  
         // Look up the initial state for the specified dialog
 702  0
         State state = dialog.findState(dialog.getStart());
 703  0
         if (state == null) {
 704  0
             throw new IllegalStateException
 705  
               ("Cannot find starting state '"
 706  
                + dialog.getStart()
 707  
                + "' for dialog '" + dialog.getName() + "'");
 708  
         }
 709  
 
 710  
         // Construct an appropriate data object for the specified dialog
 711  0
         Object data = null;
 712  
         try {
 713  0
             data = dialog.getDataClass().newInstance();
 714  0
         } catch (Exception e) {
 715  0
             fireOnException(e);
 716  0
         }
 717  
 
 718  
         // Push a Position and corresponding data object to our stacks
 719  0
         push(new Position(dialog, state, data));
 720  
 
 721  
         // inform listeners
 722  0
         fireOnEntry(state.getName());
 723  
 
 724  0
     }
 725  
 
 726  
 
 727  
     /**
 728  
      * <p>Return the strategy identifier for dealing with saving state
 729  
      * information across requests.</p>
 730  
      */
 731  
     private String strategy() {
 732  
 
 733  0
         if (this.strategy == null) {
 734  0
             this.strategy = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(Globals.STRATEGY);
 735  0
             if (this.strategy == null) {
 736  0
                 this.strategy = "top";
 737  0
             } else {
 738  0
                 this.strategy = this.strategy.toLowerCase();
 739  
             }
 740  
         }
 741  0
         return this.strategy;
 742  
 
 743  
     }
 744  
 
 745  
 
 746  
     /**
 747  
      * <p>Transition the specified {@link Position}, based on the specified
 748  
      * logical outcome, to the appropriate next {@link State}.</p>
 749  
      *
 750  
      * @param position {@link Position} to be transitioned
 751  
      * @param outcome Logical outcome to use for transitioning
 752  
      */
 753  
     private void transition(Position position, String outcome) {
 754  
 
 755  
         // If the outcome is null, stay on the same state to be consistent
 756  
         // with JSF semantics on null outcomes
 757  0
         if (outcome == null) {
 758  0
             return;
 759  
         }
 760  
 
 761  0
         State current = position.getState();
 762  0
         String fromStateId = current.getName();
 763  
 
 764  
         // Select the next state based on the specified outcome
 765  0
         Transition transition = current.findTransition(outcome);
 766  0
         if (transition == null) {
 767  0
             transition = position.getDialog().findTransition(outcome);
 768  
         }
 769  0
         if (transition == null) {
 770  0
             throw new IllegalStateException
 771  
               ("Cannot find transition '" + outcome
 772  
                + "' for state '" + fromStateId
 773  
                + "' of dialog '" + position.getDialog().getName() + "'");
 774  
         }
 775  0
         State next = position.getDialog().findState(transition.getTarget());
 776  0
         if (next == null) {
 777  0
             throw new IllegalStateException
 778  
               ("Cannot find state '" + transition.getTarget()
 779  
                + "' for dialog '" + position.getDialog().getName() + "'");
 780  
         }
 781  0
         String toStateId = next.getName();
 782  0
         position.setState(next);
 783  
 
 784  
         // We inform listeners at the end, the assumption being that a transition
 785  
         // must be an atomic transaction, either all associated callbacks must
 786  
         // occur as the application would expect, or none should be initiated
 787  
 
 788  
         // On a temporal axis, we fire the source state exit handlers first ...
 789  0
         fireOnExit(fromStateId);
 790  
 
 791  
         // ... followed by the transition handlers ...
 792  0
         fireOnTransition(fromStateId, toStateId);
 793  
 
 794  
         // ... and finally, the target state entry handlers
 795  0
         fireOnEntry(toStateId);
 796  
 
 797  0
     }
 798  
 
 799  
 
 800  
     // ------------------------------------------------ State Storage Subclasses
 801  
 
 802  
 
 803  
     /**
 804  
      * <p>Class that represents the saved opaque state information when the
 805  
      * <code>top</code> strategy is selected.</p>
 806  
      */
 807  
     public class TopState implements Serializable {
 808  
 
 809  
         /**
 810  
          * Serial version UID.
 811  
          */
 812  
         private static final long serialVersionUID = 1L;
 813  
 
 814  
 
 815  
         /**
 816  
          * <p>Construct an uninitialized instance of this state class.</p>
 817  
          */
 818  0
         public TopState() {
 819  
             ;
 820  0
         }
 821  
 
 822  
 
 823  
         /**
 824  
          * <p>Construct an initialized instance of this state class.</p>
 825  
          *
 826  
          * @param stateName Name of the current state in the topmost position
 827  
          * @param stackDepth Depth of the position stack
 828  
          */
 829  0
         public TopState(String stateName, int stackDepth) {
 830  0
             this.stateName = stateName;
 831  0
             this.stackDepth = stackDepth;
 832  0
         }
 833  
 
 834  
 
 835  
         /**
 836  
          * <p>The name of the current state within the topmost
 837  
          * <code>Position</code> on the stack.</p>
 838  
          */
 839  
         public String stateName;
 840  
 
 841  
 
 842  
         /**
 843  
          * <p>The stack depth of the <code>Position</code> stack.  This is
 844  
          * used to detect scenarios where using the back and forward buttons
 845  
          * navigates across a subdialog boundary, which means that the
 846  
          * saved <code>stateName</code> is likely no longer relevant.</p>
 847  
          */
 848  
         public int stackDepth;
 849  
 
 850  
 
 851  
         /**
 852  
          * <p>Return a string representation of this instance.</p>
 853  
          */
 854  
         public String toString() {
 855  0
             return "TopState[stateName=" + this.stateName
 856  
                    + ", stackDepth=" + this.stackDepth + "]";
 857  
         }
 858  
 
 859  
 
 860  
 
 861  
     }
 862  
 
 863  
 
 864  
 
 865  
 }