/* ServletContextLocator.java

	Purpose:
		
	Description:
		
	History:
		Wed Jul  6 15:16:05     2005, Created by tomyeh

Copyright (C) 2005 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.web.util.resource;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;

import javax.servlet.ServletContext;

import org.zkoss.lang.SystemException;
import org.zkoss.util.resource.Locator;
import org.zkoss.web.servlet.Servlets;
import org.zkoss.web.servlet.http.Https;

/**
 * Locator based on ServletContext.
 *
 * @author tomyeh
 */
public class ServletContextLocator implements Locator {
	private final ServletContext _ctx;
	private final String _dir, _prefix;
	private final boolean _acceptURL;
	private final String _externalPrefix;

	/** Constructor.
	 * A short cut of ServletContextLocator(ctx, null, null, false)
	 */
	public ServletContextLocator(ServletContext ctx) {
		this(ctx, null, null, false, null);
	}

	/** Constructor.
	 * @param acceptURL whether to URL (such as file:/, http:// and
	 * ftp://) are accepted. In other words, {@link Servlets#getResource}
	 * will be used.
	 * @see Servlets#getResource
	 * @since 5.0.7
	 */
	public ServletContextLocator(ServletContext ctx, boolean acceptURL) {
		this(ctx, null, null, acceptURL, null);
	}

	/** Constructor.
	 * A short of ServletContextLocator(ctx, dir, null, false, null).
	 * @param dir the directory used when relative path is specified
	 * (for {@link #getResource} and {@link #getResourceAsStream}).
	 * It must be null, empty, or starts with /.
	 */
	public ServletContextLocator(ServletContext ctx, String dir) {
		this(ctx, dir, null, false, null);
	}

	/** Constructor.
	 * A short cut of ServletContextLocator(ctx, dir, prefix, false, null).
	 */
	public ServletContextLocator(ServletContext ctx, String dir, String prefix) {
		this(ctx, dir, prefix, false, null);
	}

	/** Constructor.
	 * A short cut of ServletContextLocator(ctx, dir, prefix, acceptURL, null).
	 */
	public ServletContextLocator(ServletContext ctx, String dir, String prefix, boolean acceptURL) {
		this(ctx, dir, prefix, acceptURL, null);
	}

	/** Constructor.
	 * For example, if prefix is "/WEB-INF/cwr", then getResource("/abc") will
	 * look for "/WEB-INF/cwr/abc" first, and then "/abc".
	 *
	 * <p>Another example, if prefix is "/WEB-INF/cwr" and dir is "/subdir",
	 * then getResource("abc") will look for "/WEB-INF/cwr/subdir/abc".
	 *
	 * @param dir the directory used when relative path is specified
	 * (for {@link #getResource} and {@link #getResourceAsStream}).
	 * It must be null, empty, or starts with /.
	 * @param prefix the directory to prefix each directory specified
	 * (for {@link #getResource} and {@link #getResourceAsStream}).
	 * It must be null, empty, or starts with /.
	 * @param acceptURL whether to URL (such as file:/, http:// and
	 * ftp://) are accepted. In other words, {@link Servlets#getResource}
	 * will be used.
	 */
	public ServletContextLocator(ServletContext ctx, String dir, String prefix, boolean acceptURL, String externalPrefix) {
		if (ctx == null)
			throw new IllegalArgumentException("null");
		if (dir != null) {
			final int len = dir.length();
			if (len == 0)
				dir = null;
			else {
				if (dir.charAt(0) != '/')
					throw new IllegalArgumentException("Absolute path required: " + dir);
				if (dir.charAt(len - 1) != '/')
					dir += '/';
			}
		}
		if (prefix != null) {
			final int len = prefix.length();
			if (len == 0)
				prefix = null;
			else {
				if (prefix.charAt(0) != '/')
					throw new IllegalArgumentException("Absolute path required: " + prefix);
				if (len == 1)
					prefix = null; // "/" only
				else if (prefix.charAt(len - 1) == '/')
					prefix = prefix.substring(0, len - 1);
			}
		}

		_ctx = ctx;
		_dir = dir;
		_prefix = prefix;
		_acceptURL = acceptURL;
		_externalPrefix = externalPrefix;
	}

	/** Returns the servlet context. */
	public ServletContext getServletContext() {
		return _ctx;
	}

	private String fixName(String name, boolean prefix) {
		name = name.length() > 0 && name.charAt(0) != '/'
				? _dir != null ? _dir + name : prefix && _prefix != null ? '/' + name : name : name;
		return prefix && _prefix != null ? _prefix + name : (_externalPrefix == null ?  "" : _externalPrefix) + name;
	}

	//-- Locator --//
	public String getDirectory() {
		return _dir;
	}

	public URL getResource(String name) {
		String relativePath = null;
		try {
			URL url = getResource0(relativePath = fixName(name, true));
			return url == null && _prefix != null ? getResource0(relativePath = fixName(name, false)) : url;
		} catch (java.net.MalformedURLException ex) {
			if (relativePath != null && !relativePath.startsWith("-")) {
				return null; // ignore relative path here for ZK-5215
			}
			throw new SystemException(ex);
		} catch (UnsupportedEncodingException e) {
			throw new SystemException(e);
		}
	}

	public InputStream getResourceAsStream(String name) {
		try {
			InputStream is = getResourceAsStream0(fixName(name, true));
			return is == null && _prefix != null ? getResourceAsStream0(fixName(name, false)) : is;
		} catch (java.io.IOException ex) {
			throw new SystemException(ex);
		}
	}

	private URL getResource0(String path) throws java.net.MalformedURLException, UnsupportedEncodingException {
		return _acceptURL ? Servlets.getResource(_ctx, path) : _ctx.getResource(Https.sanitizePath(path));
	}

	private InputStream getResourceAsStream0(String path) throws java.io.IOException {
		return _acceptURL ? Servlets.getResourceAsStream(_ctx, path) : _ctx.getResourceAsStream(Https.sanitizePath(path));
	}

	//-- Object --//
	public int hashCode() {
		return _ctx.hashCode();
	}

	public boolean equals(Object o) {
		if (this == o)
			return true;
		return o instanceof ServletContextLocator && ((ServletContextLocator) o)._ctx.equals(_ctx);
	}
}
