/*
 * 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 freemarker.cache;

import java.net.URL;

import freemarker.template.utility.NullArgumentException;
import freemarker.template.utility.StringUtil;

/**
 * A {@link TemplateLoader} that can load templates from the "classpath". Naturally, it can load from jar files, or from
 * anywhere where Java can load classes from. Internally, it uses {@link Class#getResource(String)} or
 * {@link ClassLoader#getResource(String)} to load templates.
 */
public class ClassTemplateLoader extends URLTemplateLoader {
    
    private final Class<?> resourceLoaderClass;
    private final ClassLoader classLoader;
    private final String basePackagePath;

    /**
     * Creates a template loader that will use the {@link Class#getResource(String)} method of its own class to load the
     * resources, and {@code "/"} as base package path. This means that that template paths will be resolved relatively
     * the root package of the class hierarchy, so you hardly ever should use this constructor, rather do something like
     * this:<br>
     * {@link #ClassTemplateLoader(Class, String) new ClassTemplateLoader(com.example.myapplication.SomeClass.class,
     * "templates")}
     *
     * <p>
     * If you extend this class, then the extending class will be used to load the resources.
     *
     * @deprecated It's a confusing constructor, and seldom useful; use {@link #ClassTemplateLoader(Class, String)}
     *             instead.
     */
    @Deprecated
    public ClassTemplateLoader() {
        this(null, true, null, "/");
    }

    /**
     * Creates a template loader that will use the {@link Class#getResource(String)} method of the specified class to
     * load the resources, and {@code ""} as base package path. This means that template paths will be resolved
     * relatively to the class location, that is, relatively to the directory (package) of the class.
     *
     * @param resourceLoaderClass
     *            the class whose {@link Class#getResource(String)} will be used to load the templates.
     *
     * @deprecated It's confusing that the base path is {@code ""}; use {@link #ClassTemplateLoader(Class, String)}
     *             instead.
     */
    @Deprecated
    public ClassTemplateLoader(Class<?> resourceLoaderClass) {
        this(resourceLoaderClass, "");
    }

    /**
     * Creates a template loader that will use the {@link Class#getResource(String)} method of the specified class to
     * load the resources, and the specified base package path (absolute or relative).
     *
     * <p>
     * Examples:
     * <ul>
     * <li>Relative base path (will load from the {@code com.example.myapplication.templates} package):<br>
     * {@code new ClassTemplateLoader(com.example.myapplication.SomeClass.class, "templates")}
     * <li>Absolute base path:<br>
     * {@code new ClassTemplateLoader(somepackage.SomeClass.class, "/com/example/myapplication/templates")}
     * </ul>
     *
     * @param resourceLoaderClass
     *            The class whose {@link Class#getResource(String)} method will be used to load the templates. Be sure
     *            that you chose a class whose defining class-loader sees the templates. This parameter can't be
     *            {@code null}.
     * @param basePackagePath
     *            The package that contains the templates, in path ({@code /}-separated) format. If it doesn't start
     *            with a {@code /} then it's relative to the path (package) of the {@code resourceLoaderClass} class. If
     *            it starts with {@code /} then it's relative to the root of the package hierarchy. Note that path
     *            components should be separated by forward slashes independently of the separator character used by the
     *            underlying operating system. This parameter can't be {@code null}.
     * 
     * @see #ClassTemplateLoader(ClassLoader, String)
     */
    public ClassTemplateLoader(Class<?> resourceLoaderClass, String basePackagePath) {
        this(resourceLoaderClass, false, null, basePackagePath);
    }

    /**
     * Similar to {@link #ClassTemplateLoader(Class, String)}, but instead of {@link Class#getResource(String)} it uses
     * {@link ClassLoader#getResource(String)}. Because a {@link ClassLoader} isn't bound to any Java package, it
     * doesn't mater if the {@code basePackagePath} starts with {@code /} or not, it will be always relative to the root
     * of the package hierarchy
     * 
     * @since 2.3.22
     */
    public ClassTemplateLoader(ClassLoader classLoader, String basePackagePath) {
        this(null, true, classLoader, basePackagePath);
    }

    private ClassTemplateLoader(Class<?> resourceLoaderClass, boolean allowNullResourceLoaderClass,
            ClassLoader classLoader, String basePackagePath) {
        if (!allowNullResourceLoaderClass) {
            NullArgumentException.check("resourceLoaderClass", resourceLoaderClass);
        }
        NullArgumentException.check("basePackagePath", basePackagePath);

        // Either set a non-null resourceLoaderClass or a non-null classLoader, not both:
        this.resourceLoaderClass = classLoader == null ? (resourceLoaderClass == null ? this.getClass()
                : resourceLoaderClass) : null;
        if (this.resourceLoaderClass == null && classLoader == null) {
            throw new NullArgumentException("classLoader");
        }
        this.classLoader = classLoader;

        String canonBasePackagePath = canonicalizePrefix(basePackagePath);
        if (this.classLoader != null && canonBasePackagePath.startsWith("/")) {
            canonBasePackagePath = canonBasePackagePath.substring(1);
        }
        this.basePackagePath = canonBasePackagePath;
    }

    @Override
    protected URL getURL(String name) {
        String fullPath = basePackagePath + name;

        // Block java.net.URLClassLoader exploits:
        if (basePackagePath.equals("/") && !isSchemeless(fullPath)) {
            return null;
        }

        return resourceLoaderClass != null ? resourceLoaderClass.getResource(fullPath) : classLoader
                .getResource(fullPath);
    }

    private static boolean isSchemeless(String fullPath) {
        int i = 0;
        int ln = fullPath.length();

        // Skip a single initial /, as things like "/file:/..." might work:
        if (i < ln && fullPath.charAt(i) == '/') i++;

        // Check if there's no ":" earlier than a '/', as the URLClassLoader
        // could interpret that as an URL scheme:
        while (i < ln) {
            char c = fullPath.charAt(i);
            if (c == '/') return true;
            if (c == ':') return false;
            i++;
        }
        return true;
    }

    /**
     * Show class name and some details that are useful in template-not-found errors.
     * 
     * @since 2.3.21
     */
    @Override
    public String toString() {
        return TemplateLoaderUtils.getClassNameForToString(this) + "("
                + (resourceLoaderClass != null
                        ? "resourceLoaderClass=" + resourceLoaderClass.getName()
                        : "classLoader=" + StringUtil.jQuote(classLoader))
                + ", basePackagePath"
                + "="
                + StringUtil.jQuote(basePackagePath)
                + (resourceLoaderClass != null
                        ? (basePackagePath.startsWith("/") ? "" : " /* relatively to resourceLoaderClass pkg */")
                        : ""
                )
                + ")";
    }

    /**
     * See the similar parameter of {@link #ClassTemplateLoader(Class, String)}; {@code null} when other mechanism is
     * used to load the resources.
     * 
     * @since 2.3.22
     */
    public Class getResourceLoaderClass() {
        return resourceLoaderClass;
    }

    /**
     * See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; {@code null} when other mechanism
     * is used to load the resources.
     * 
     * @since 2.3.22
     */
    public ClassLoader getClassLoader() {
        return classLoader;
    }

    /**
     * See the similar parameter of {@link #ClassTemplateLoader(ClassLoader, String)}; note that this is a normalized
     * version of what was actually passed to the constructor.
     * 
     * @since 2.3.22
     */
    public String getBasePackagePath() {
        return basePackagePath;
    }

}