/*
 * Copyright (c) 2007, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

/*
 * $Id$
 */

package com.sun.ts.tests.jsp.spec.core_syntax.scripting.el;

import com.sun.ts.tests.jsp.common.util.JspTestUtil;

import jakarta.servlet.jsp.tagext.TagSupport;
import jakarta.servlet.jsp.JspException;
import jakarta.servlet.jsp.JspWriter;
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;

/**
 * Base class to perform validation of the JSP 2.0 expression language. This
 * class provides facilities to set a control object which a test object is
 * compared against. It's up to the concrete implementation of this class to
 * provided the logic to handle the validation.
 */
public abstract class BaseCheckTag extends TagSupport {

  /**
   * The implicit EL object under test
   */
  protected Object _object = null;

  /**
   * The name of the implicit object under test
   */
  protected String _name = null;

  /**
   * The object to compare against the implicit object provided by the
   * container.
   */
  protected Object _control = null;

  /**
   * String containing a message about failure cause.
   */
  private String _message = null;

  /**
   * If true, the tag will display any message currently set.
   */
  private boolean _display = false;

  /**
   * Returns the current message.
   * 
   * @return the current message or null of there is no failure
   */
  public String getMessage() {
    return _message;
  }

  /**
   * Will cause any error message to be displayed.
   * 
   * @param display
   */
  public void setDisplay(boolean display) {
    _display = display;
  }

  /**
   * Sets the object under test.
   * 
   * @param o
   *          - the implicit object under test
   */
  public void setObject(Object o) {
    _object = o;
  }

  /**
   * The name of the implicit object under test
   * 
   * @param name
   *          - the name of the implicit object
   */
  public void setName(String name) {
    _name = name;
  }

  /**
   * Sets the control object for the test.
   * 
   * @param o
   *          - the control object
   */
  public void setControl(Object o) {
    _control = o;
  }

  /**
   * Performs the implemented check provided by the base classes.
   * 
   * @return EVAL_BODY_INCLUDE
   * @throws JspException
   *           if an error occurs
   */
  public int doStartTag() throws JspException {
    performCheck();
    return EVAL_BODY_INCLUDE;
  }

  /**
   * Implemented by concrete validation tags to perform validation against the
   * provided control and test objects.
   *
   * @throws JspException
   *           - if an error occurs
   */
  protected abstract void performCheck() throws JspException;

  /**
   * Displays the status of the current test, or set of nested tests. If a
   * particular tag has the <tt>display</tt> attribute set to <tt>true</tt>,
   * this method will recurse though it's ancestor tags and obtain any failure
   * notifications.
   *
   * If this particular instance doesn't have the display attribute set, then
   * the error generated by the check will be stored to be retrieved by another
   * action nested further down.
   *
   * @param message
   *          - the message to display or store
   * @throws JspException
   *           - if an error occurs
   */
  protected void displayTestStatus(String message) throws JspException {
    if (_display) {
      JspWriter out = pageContext.getOut();
      try {
        out.println();
        String[] messages = checkAncestorsForErrors();
        if (messages.length == 0) {
          out.println("Test PASSED");
        } else {
          for (int i = 0; i < messages.length; i++) {
            out.println(messages[i]);
          }
        }
      } catch (IOException ioe) {
        throw new JspException("Unexpected IOException!", ioe);
      }
    } else {
      _message = message;
    }
  }

  /**
   * Recurses through any available ancestor tags and retrieves any error
   * messages they may have. If the ancestor returns null for the message, then
   * the test succeeded, otherwise take the returned message and add it to a
   * list to be displayed by the caller.
   * 
   * @return a String array of failure messages
   */
  private String[] checkAncestorsForErrors() {
    List list = new ArrayList();
    debug("Checking parent tags for errors...");
    for (BaseCheckTag parent = (BaseCheckTag) this
        .getParent(); parent != null; parent = (BaseCheckTag) parent
            .getParent()) {
      String message = parent.getMessage();
      if (message != null) {
        debug("Error: " + message);
        list.add(message);
      }
      debug("Check of parent tag [" + parent + "] completed.");
    }
    return (String[]) list.toArray(new String[list.size()]);
  }

  /**
   * Wrapper method for JspTestUtil.debug(String). This wrapper will prepend
   * this classes name to the provided message.
   * 
   * @param message
   *          - the debug message
   */
  private static void debug(String message) {
    JspTestUtil.debug("[BaseCheckTag] " + message);
  }
}
