/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jakarta.servlet.jsp.tagext;

import java.io.IOException;

import jakarta.servlet.jsp.JspContext;
import jakarta.servlet.jsp.JspException;
import jakarta.servlet.jsp.SkipPageException;

/**
 * A base class for defining tag handlers implementing SimpleTag.
 * <p>
 * The SimpleTagSupport class is a utility class intended to be used as the base class for new simple tag handlers. The
 * SimpleTagSupport class implements the SimpleTag interface and adds additional convenience methods including getter
 * methods for the properties in SimpleTag.
 *
 * @since JSP 2.0
 */
public class SimpleTagSupport implements SimpleTag {
    /** Reference to the enclosing tag. */
    private JspTag parentTag;

    /** The JSP context for the upcoming tag invocation. */
    private JspContext jspContext;

    /** The body of the tag. */
    private JspFragment jspBody;

    /**
     * Sole constructor. (For invocation by subclass constructors, typically implicit.)
     */
    public SimpleTagSupport() {
        // NOOP by default
    }

    /**
     * Default processing of the tag does nothing.
     *
     * @throws JspException      Subclasses can throw JspException to indicate an error occurred while processing this
     *                               tag.
     * @throws SkipPageException If the page that (either directly or indirectly) invoked this tag is to cease
     *                               evaluation. A Simple Tag Handler generated from a tag file must throw this
     *                               exception if an invoked Classic Tag Handler returned SKIP_PAGE or if an invoked
     *                               Simple Tag Handler threw SkipPageException or if an invoked Jsp Fragment threw a
     *                               SkipPageException.
     * @throws IOException       Subclasses can throw IOException if there was an error writing to the output stream
     *
     * @see SimpleTag#doTag()
     */
    @Override
    public void doTag() throws JspException, IOException {
        // NOOP by default
    }

    /**
     * Sets the parent of this tag, for collaboration purposes.
     * <p>
     * The container invokes this method only if this tag invocation is nested within another tag invocation.
     *
     * @param parent the tag that encloses this tag
     */
    @Override
    public void setParent(JspTag parent) {
        this.parentTag = parent;
    }

    /**
     * Returns the parent of this tag, for collaboration purposes.
     *
     * @return the parent of this tag
     */
    @Override
    public JspTag getParent() {
        return this.parentTag;
    }

    /**
     * Stores the provided JSP context in the private jspContext field. Subclasses can access the
     * <code>JspContext</code> via <code>getJspContext()</code>.
     *
     * @param pc the page context for this invocation
     *
     * @see SimpleTag#setJspContext
     */
    @Override
    public void setJspContext(JspContext pc) {
        this.jspContext = pc;
    }

    /**
     * Returns the page context passed in by the container via setJspContext.
     *
     * @return the page context for this invocation
     */
    protected JspContext getJspContext() {
        return this.jspContext;
    }

    /**
     * Stores the provided JspFragment.
     *
     * @param jspBody The fragment encapsulating the body of this tag. If the action element is empty in the page, this
     *                    method is not called at all.
     *
     * @see SimpleTag#setJspBody
     */
    @Override
    public void setJspBody(JspFragment jspBody) {
        this.jspBody = jspBody;
    }

    /**
     * Returns the body passed in by the container via setJspBody.
     *
     * @return the fragment encapsulating the body of this tag, or null if the action element is empty in the page.
     */
    protected JspFragment getJspBody() {
        return this.jspBody;
    }

    /**
     * Find the instance of a given class type that is closest to a given instance. This method uses the getParent
     * method from the Tag and/or SimpleTag interfaces. This method is used for coordination among cooperating tags.
     * <p>
     * For every instance of TagAdapter encountered while traversing the ancestors, the tag handler returned by
     * <code>TagAdapter.getAdaptee()</code> - instead of the TagAdapter itself - is compared to <code>klass</code>. If
     * the tag handler matches, it - and not its TagAdapter - is returned.
     * <p>
     * The current version of the specification only provides one formal way of indicating the observable type of a tag
     * handler: its tag handler implementation class, described in the tag-class subelement of the tag element. This is
     * extended in an informal manner by allowing the tag library author to indicate in the description subelement an
     * observable type. The type should be a subtype of the tag handler implementation class or void. This additional
     * constraint can be exploited by a specialized container that knows about that specific tag library, as in the case
     * of the JSP standard tag library.
     * <p>
     * When a tag library author provides information on the observable type of a tag handler, client programmatic code
     * should adhere to that constraint. Specifically, the Class passed to findAncestorWithClass should be a subtype of
     * the observable type.
     *
     * @param from  The instance from where to start looking.
     * @param klass The subclass of JspTag or interface to be matched
     *
     * @return the nearest ancestor that implements the interface or is an instance of the class specified
     */
    public static final JspTag findAncestorWithClass(JspTag from, Class<?> klass) {
        boolean isInterface = false;

        if (from == null || klass == null ||
                !JspTag.class.isAssignableFrom(klass) && !(isInterface = klass.isInterface())) {
            return null;
        }

        for (;;) {
            JspTag parent = null;
            if (from instanceof SimpleTag) {
                parent = ((SimpleTag) from).getParent();
            } else if (from instanceof Tag) {
                parent = ((Tag) from).getParent();
            }
            if (parent == null) {
                return null;
            }

            if (parent instanceof TagAdapter) {
                parent = ((TagAdapter) parent).getAdaptee();
            }

            if (isInterface && klass.isInstance(parent) || klass.isAssignableFrom(parent.getClass())) {
                return parent;
            }

            from = parent;
        }
    }
}
