/*
 * 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 org.apache.tomcat.util.descriptor.tld;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Objects;

import org.apache.tomcat.Jar;
import org.apache.tomcat.util.scan.JarFactory;
import org.apache.tomcat.util.scan.ReferenceCountedJar;

/**
 * A TLD Resource Path as defined in JSP 7.3.2.
 * <p>
 * This encapsulates references to Tag Library Descriptors that can be located in different places:
 * <ul>
 * <li>As resources within an application</li>
 * <li>As entries in JAR files included in the application</li>
 * <li>As resources provided by the container</li>
 * </ul>
 * When configuring a mapping from a well-known URI to a TLD, a user is allowed to specify just the name of a JAR file
 * that implicitly contains a TLD in <code>META-INF/taglib.tld</code>. Such a mapping must be explicitly converted to a
 * URL and entryName when using this implementation.
 */
public class TldResourcePath {
    private final URL url;
    private final String webappPath;
    private final String entryName;

    /**
     * Constructor identifying a TLD resource directly.
     *
     * @param url        the location of the TLD
     * @param webappPath the web application path, if any, of the TLD
     */
    public TldResourcePath(URL url, String webappPath) {
        this(url, webappPath, null);
    }

    /**
     * Constructor identifying a TLD packaged within a JAR file.
     *
     * @param url        the location of the JAR
     * @param webappPath the web application path, if any, of the JAR
     * @param entryName  the name of the entry in the JAR
     */
    public TldResourcePath(URL url, String webappPath, String entryName) {
        this.url = url;
        this.webappPath = webappPath;
        this.entryName = entryName;
    }

    /**
     * Returns the URL of the TLD or of the JAR containing the TLD.
     *
     * @return the URL of the TLD
     */
    public URL getUrl() {
        return url;
    }

    /**
     * Returns the path within the web application, if any, that the resource returned by {@link #getUrl()} was obtained
     * from.
     *
     * @return the web application path or @null if the resource is not located within a web application
     */
    public String getWebappPath() {
        return webappPath;
    }

    /**
     * Returns the name of the JAR entry that contains the TLD. May be null to indicate the URL refers directly to the
     * TLD itself.
     *
     * @return the name of the JAR entry that contains the TLD
     */
    public String getEntryName() {
        return entryName;
    }

    /**
     * Return the external form of the URL representing this TLD. This can be used as a canonical location for the TLD
     * itself, for example, as the systemId to use when parsing its XML.
     *
     * @return the external form of the URL representing this TLD
     */
    public String toExternalForm() {
        if (entryName == null) {
            return url.toExternalForm();
        } else {
            return "jar:" + url.toExternalForm() + "!/" + entryName;
        }
    }

    /**
     * Opens a stream to access the TLD.
     *
     * @return a stream containing the TLD content
     *
     * @throws IOException if there was a problem opening the stream
     */
    public InputStream openStream() throws IOException {
        if (entryName == null) {
            return url.openStream();
        } else {
            URL entryUrl = JarFactory.getJarEntryURL(url, entryName);
            return entryUrl.openStream();
        }
    }

    /**
     * Opens the JAR file that contains this resource, if any.
     *
     * @return the JAR, or null if this is not a JAR entry
     * @throws IOException if an I/O error occurs
     */
    public Jar openJar() throws IOException {
        if (entryName == null) {
            return null;
        } else {
            // Bug 62976
            // Jar files containing tags are typically opened during initial
            // compilation and then closed when compilation is complete. The
            // reference counting wrapper is used because, when background
            // compilation is enabled, the Jar will need to be accessed (to
            // check for modifications) after it has been closed at the end
            // of the compilation stage.
            // Using a reference counted Jar enables the Jar to be re-opened,
            // used and then closed again rather than triggering an ISE.
            return new ReferenceCountedJar(url);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        TldResourcePath other = (TldResourcePath) o;

        return url.equals(other.url) && Objects.equals(webappPath, other.webappPath) &&
                Objects.equals(entryName, other.entryName);
    }

    @Override
    public int hashCode() {
        int result = url.hashCode();
        result = result * 31 + Objects.hashCode(webappPath);
        result = result * 31 + Objects.hashCode(entryName);
        return result;
    }
}
