/* Locators.java


	Purpose:
	Description:
	History:
	90/12/07 10:34:55, Create, Tom M. Yeh.

Copyright (C) 2001 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.util.resource;

import java.io.InputStream;
import java.net.URL;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.util.Locales;

/**
 * Utilities to load and locate resources.
 *
 * @author tomyeh
 * @see Locator
 */
public class Locators {
	private static final Logger log = LoggerFactory.getLogger(Locators.class);

	/** The default locator. */
	private static final Locator _locator = new ClassLocator();
	/**
	 * Returns the default resource locator which is an instance of
	 * {@link ClassLocator}.
	 */
	public static final Locator getDefault() {
		return _locator;
	}

	/**
	 * Locates the filename based on the locale.
	 *
	 * <p>Example, assume file="/a/b.ext" and locale="zh_TW", then
	 * the following files are tried in sequence:<br>
	 * /a/b_zh_TW.ext<br>
	 * /a/b_zh.ext<br>
	 * /a/b.ext
	 *
	 * <p>Unlike ClassLoader.getResource, it returns the found file,
	 * not the URL. In the previous example, one of /a/b_zh_TW.ext,
	 * /a/b_zh.ext and /a/b.ext will be returned.
	 *
	 * <p>To get the real URL, you still require locator.getResource:<br>
	 * <code>locator.getResource(Locators.locate(locator, "/a/b.ext", locale));</code>
	 *
	 * <p>Note: '_' is considered as a special character in the parsing,
	 * so there might be problem if a filename contains '_' used other
	 * than locale.
	 *
	 * <p>Unlike {@link org.zkoss.io.Files#locate}, where the filename
	 * must contain '*', this method always tries to locate the file by
	 * inserting the locale before '.'. In other words,
	 * Files.locate("/a/b*.c") is similar to
	 * Locators.locate(("/a/b.c", null, a_file_locator);
	 *
	 * @param locale the locale; null means the current locale
	 * @return the URL containing proper locale characters; null if not found.
	 * Note: it could compare it with the file argument with ==, because
	 * this method will return the same string if it is the result.
	 */
	public static final
	URLLocation locate(String file, Locale locale, Locator locator) {
		return (URLLocation)myLocate(file, locale, locator, false);
	}
	/**
	 * Locates the input stream based on the locale, and returns the info
	 * of StreamLocation.
	 *
	 * @see #locate
	 */
	public static final StreamLocation
	locateAsStream(String file, Locale locale, Locator locator) {
		return (StreamLocation)myLocate(file, locale, locator, true);
	}
	/** the location information. */
	public static class URLLocation {
		public final URL url;
		public final String file;
		public final Locale locale;
		public URLLocation(URL url, String file, Locale locale) {
			this.url = url;
			this.file = file;
			this.locale = locale;
		}
		public String toString() {
			return "[url: "+url+" file="+file+" l="+locale+']';
		}
	}
	/** the location information. */
	public static class StreamLocation {
		public final InputStream stream;
		public final Locale locale;
		public StreamLocation(InputStream stream, Locale locale) {
			this.stream = stream;
			this.locale = locale;
		}
		public String toString() {
			return "[l="+locale+']';
		}
	}
	/** Locates the file. */
	private static final Object
	myLocate(String file, Locale locale, Locator locator, boolean asStream) {
		if (locale == null)
			locale = Locales.getCurrent();

		final int jslash = file.lastIndexOf('/'); //>= -1
		final int jdot =
			jslash >= 0 ? file.indexOf('.', jslash + 1): file.indexOf('.');
		final String ext = jdot >= 0 ? file.substring(jdot): "";
		final int jul = Locales.indexOfUnderline(file, jslash >= 0 ? jslash + 1: 0);
		final String base = file.substring(0,
			jul >= 0 && (jdot < 0 || jul < jdot) ? jul:
			jdot >= 0 ? jdot : file.length());

		if (log.isDebugEnabled())
			log.debug("svl={} base={} ext={}", file, base, ext);

		//search the existence based on locale
		final int baseLen = base.length();
		final StringBuffer sb = new StringBuffer(baseLen + 16).append(base);
		final String[] args = new String[1];
		final String[] secs = new String[] {
			locale.getLanguage(), locale.getCountry(), locale.getVariant()
		};

		for (int j = secs.length; --j >= -1;) {
			if (j >= 0 && secs[j].length() == 0)
				continue;

			sb.setLength(baseLen);
			for (int k = 0; k <= j; ++k)
				sb.append('_').append(secs[k]);
			sb.append(ext);

			args[0] = sb.toString();
			final Object found =
				asStream ?
					(locator).getResourceAsStream(args[0]):
					(locator).getResource(args[0]);
			if (found != null) {
				//decide the locale
				final Locale l;
				if (j >= 0) {
					sb.setLength(0);
					for (int k = 0; k <= j; ++k) {
						if (k > 0) sb.append('_');
						sb.append(secs[k]);
					}
				 	l = Locales.getLocale(sb.toString(), '_');
				} else {
					l = null;
				}

				//return the info
				if (asStream) {
					return new StreamLocation((InputStream)found, l);
				} else {
					return new URLLocation((URL)found,
						args[0].equals(file) ? file: args[0], l);
				}
			}
		}
		return null;
	}
}
