docs: private boolean notifyListeners(FacesContext context, PhaseId phaseId, MethodExpression listener,
                                      boolean beforePhase)
      {
          List<PhaseListener> phaseListeners = (List<PhaseListener>) getStateHelper().get(PropertyKeys.phaseListeners);
          // Check if any listener was called
          boolean listenerCalled = false;
          if (listener != null || (phaseListeners != null && !phaseListeners.isEmpty()))
          {
              // how many listeners do we have? (the MethodExpression listener is counted in either way)
              // NOTE: beforePhaseSuccess[0] always refers to the MethodExpression listener
              int listenerCount = (phaseListeners != null ? phaseListeners.size() + 1 : 1);
              
              boolean[] beforePhaseSuccess;
              if (beforePhase)
              {
                  beforePhaseSuccess = new boolean[listenerCount];
                  _getListenerSuccessMap().put(phaseId, beforePhaseSuccess);
              }
              else
              {
                  // afterPhase - get beforePhaseSuccess from the Map
                  beforePhaseSuccess = _getListenerSuccessMap().get(phaseId);
                  if (beforePhaseSuccess == null)
                  {
                      // no Map available - assume that everything went well
                      beforePhaseSuccess = new boolean[listenerCount];
                      Arrays.fill(beforePhaseSuccess, true);
                  }
              }
              
              PhaseEvent event = createEvent(context, phaseId);
  
              // only invoke the listener if we are in beforePhase
              // or if the related before PhaseListener finished without an Exception
              if (listener != null && (beforePhase || beforePhaseSuccess[0]))
              {
                  listenerCalled = true;
                  try
                  {
                      listener.invoke(context.getELContext(), new Object[] { event });
                      beforePhaseSuccess[0] = true;
                  }
                  catch (Throwable t) 
                  {
                      beforePhaseSuccess[0] = false; // redundant - for clarity
                      _getLogger().log(Level.SEVERE, "An Exception occured while processing " +
                                               listener.getExpressionString() + 
                                               " in Phase " + phaseId, t);
                      if (beforePhase)
                      {
                          return context.getResponseComplete() ||
                                  (context.getRenderResponse() && !PhaseId.RENDER_RESPONSE.equals(phaseId));
                      }
                  }
              }
              else if (beforePhase)
              {
                  // there is no beforePhase MethodExpression listener
                  beforePhaseSuccess[0] = true;
              }
  
              if (phaseListeners != null && !phaseListeners.isEmpty())
              {
                  if (beforePhase)
                  {
                      // process listeners in ascending order
                      for (int i = 0; i < beforePhaseSuccess.length - 1; i++)
                      {
                          PhaseListener phaseListener;
                          try 
                          {
                              phaseListener = phaseListeners.get(i);
                          }
                          catch (IndexOutOfBoundsException e)
                          {
                              // happens when a PhaseListener removes another PhaseListener 
                              // from UIViewRoot in its beforePhase method
                              throw new IllegalStateException("A PhaseListener must not remove " +
                                      "PhaseListeners from UIViewRoot.");
                          }
                          PhaseId listenerPhaseId = phaseListener.getPhaseId();
                          if (phaseId.equals(listenerPhaseId) || PhaseId.ANY_PHASE.equals(listenerPhaseId))
                          {
                              listenerCalled = true;
                              try
                              {
                                  phaseListener.beforePhase(event);
                                  beforePhaseSuccess[i + 1] = true;
                              }
                              catch (Throwable t) 
                              {
                                  beforePhaseSuccess[i + 1] = false; // redundant - for clarity
                                  _getLogger().log(Level.SEVERE, "An Exception occured while processing the " +
                                                           "beforePhase method of PhaseListener " + phaseListener +
                                                           " in Phase " + phaseId, t);
                                  return context.getResponseComplete() ||
                                          (context.getRenderResponse() && !PhaseId.RENDER_RESPONSE.equals(phaseId));
                              }
                          }
                      }
                  }
                  else
                  {
                      // afterPhase
                      // process listeners in descending order
                      for (int i = beforePhaseSuccess.length - 1; i > 0; i--)
                      {
                          PhaseListener phaseListener;
                          try 
                          {
                              phaseListener = phaseListeners.get(i - 1);
                          }
                          catch (IndexOutOfBoundsException e)
                          {
                              // happens when a PhaseListener removes another PhaseListener 
                              // from UIViewRoot in its beforePhase or afterPhase method
                              throw new IllegalStateException("A PhaseListener must not remove " +
                                      "PhaseListeners from UIViewRoot.");
                          }
                          PhaseId listenerPhaseId = phaseListener.getPhaseId();
                          if ((phaseId.equals(listenerPhaseId) || PhaseId.ANY_PHASE.equals(listenerPhaseId))
                                  && beforePhaseSuccess[i])
                          {
                              listenerCalled = true;
                              try
                              {
                                  phaseListener.afterPhase(event);
                              }
                              catch (Throwable t) 
                              {
                                  logger.log(Level.SEVERE, "An Exception occured while processing the " +
                                                           "afterPhase method of PhaseListener " + phaseListener +
                                                           " in Phase " + phaseId, t);
                              }
                          }
                      }
                  }
              }
          }
  
          // The spec javadoc says "... Upon return from the listener, call FacesContext.getResponseComplete() 
          // and FacesContext.getRenderResponse(). If either return true set the internal state flag to true. ..."
          // and later it says:
          // "... Execute any processing for this phase if the internal state flag was not set. ..."
          // But after some testing it seems if the internal state flag is not set, the check is not done and the
          // phase is not skipped. The only exception is in render response phase.
          if (listenerCalled)
          {
              if (beforePhase)
              {
                  return context.getResponseComplete() ||
                          (context.getRenderResponse() && !PhaseId.RENDER_RESPONSE.equals(phaseId));
              }
              else
              {
                  return context.getResponseComplete() || context.getRenderResponse();
              }
          }
          else
          {
              if (beforePhase)
              {
                  if (PhaseId.RENDER_RESPONSE.equals(phaseId))
                  {
                      return context.getResponseComplete();
                  }
                  else
                  {
                      // Don't check and don't skip
                      return false;
                  }
              }
              else
              {
                  // Note if is afterPhase the return value is not relevant.
                  return context.getResponseComplete() || context.getRenderResponse();
              }
          }
      }