/* 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=" + file + " base=" + base + " ext=" + 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; } }