All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.i18n.bundle.I18nBundleUtil Maven / Gradle / Ivy

There is a newer version: 4.2
Show newest version
/*
 * #%L
 * I18n :: Api
 * *
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2004 - 2010 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.i18n.bundle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.i18n.I18nUtil;

import java.io.File;
import java.io.FileInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Usefull methods on bundles.
 *
 * Note: Replace the previous class {@code org.nuiton.i18n.bundle.I18nBundleFactory}.
 *
 * @author Tony Chemit - [email protected]
 * @since 1.1
 */
public class I18nBundleUtil {

    /** Logger. */
    private static final Log log = LogFactory.getLog(I18nBundleUtil.class);

    public static final String DIRECTORY_SEARCH_BUNDLE_PATTERN = "i18n";

    private static final I18nBundleEntry[] EMPTY_I18N_BUNDLE_ENTRYS_ARRAY =
            new I18nBundleEntry[0];

    public static final URL[] EMPTY_URL_ARRAY = new URL[0];

    /**
     * Récuperation de toutes les locales connus par un ensemble de bundles.
     *
     * @param bundles les bundles a parcourir
     * @return la liste des locales rencontrées
     */
    public static Locale[] getLocales(I18nBundle... bundles) {
        Set result = new HashSet();
        for (I18nBundle i18nBundle : bundles) {
            for (I18nBundleEntry entry : i18nBundle.getEntries()) {
                Locale o = entry.getLocale();
                if (o != null) {
                    result.add(o);
                }
            }
        }
        return result.toArray(new Locale[result.size()]);
    }

    /**
     * Récuperation des noms de bundle par un ensemble de bundles.
     *
     * @param bundles les bundles a parcourir
     * @return la liste des noms de bundle rencontrées
     */
    public static String[] getBundleNames(I18nBundle... bundles) {
        List result = new ArrayList();
        for (I18nBundle i18nBundle : bundles) {
            result.add(i18nBundle.getBundlePrefix());
        }
        return result.toArray(new String[result.size()]);
    }

    /**
     * Filtrage des bundles qui correspondante à la locale donnée.
     *
     * @param l       la locale à filtrer
     * @param bundles les bundles a parcourir
     * @return les bundles qui correspondent à la locale donnée.
     */
    public static I18nBundle[] getBundles(Locale l, I18nBundle... bundles) {
        List result = new ArrayList();
        for (I18nBundle i18nBundle : bundles) {
            if (i18nBundle.matchLocale(l)) {
                result.add(i18nBundle);
            }
        }
        return result.toArray(new I18nBundle[result.size()]);
    }

    /**
     * Récupération de toutes les entrées de bundles pour les bundles données.
     *
     * @param bundles les bundles a parcourir
     * @return toutes les entrées de bundles.
     */
    public static I18nBundleEntry[] getBundleEntries(I18nBundle... bundles) {
        List result = new ArrayList();
        for (I18nBundle i18nBundle : bundles) {
            List list = i18nBundle.getEntries();
            if (!list.isEmpty()) {
                result.addAll(list);
            }
        }
        return result.toArray(new I18nBundleEntry[result.size()]);
    }

    /**
     * Filtrage des entrées de bundles pour une locale donnée.
     *
     * On essaye de trouver les meilleurs entrées possibles (possibilité de
     * promotion).
     *
     * Note: Cette méthode doit être utilisé pour trouver toutes les entrées à
     * charger par le système i18n pour une locale donnée.
     *
     * Note: Par defaut, on n'effectue pas les promotions générales ({@link
     * #getBundleEntries(Locale, Locale, boolean, I18nBundle...)}
     *
     * @param l             la locale à filtrer
     * @param defaultLocale la locale à utiliser pour les promotions
     * @param bundles       les bundles a parcourir
     * @return les entrées de bundles filtrés.
     */
    public static I18nBundleEntry[] getBundleEntries(Locale l,
                                                     Locale defaultLocale,
                                                     I18nBundle... bundles) {
        return getBundleEntries(l, defaultLocale, false, bundles);
    }

    /**
     * Filtrage des entrées de bundles pour une locale donnée.
     *
     * On essaye de trouver les meilleurs entrées possibles (possibilité de
     * promotion).
     *
     * Note: Cette méthode doit être utilisé pour trouver toutes les entrées à
     * charger par le système i18n pour une locale donnée.
     *
     * @param l              la locale à filtrer
     * @param defaultLocale  la locale à utiliser pour les promotions
     * @param promuteGeneral un drapeau pour indiquer si l'on autorise le
     *                       chargement de la locale par defaut si pour un
     *                       bundle donne on a pas trouve de traductions pour la
     *                       locale donnee.
     * @param bundles        les bundles a parcourir
     * @return les entrées de bundles filtrés.
     */
    public static I18nBundleEntry[] getBundleEntries(Locale l,
                                                     Locale defaultLocale,
                                                     boolean promuteGeneral,
                                                     I18nBundle... bundles) {

        List result = new ArrayList();
        for (I18nBundle i18nBundle : bundles) {
            I18nBundleEntry[] entries = i18nBundle.getEntries(l);
            if (entries.length == 0) {
                //no entry found for the bundle, try pomotion
                entries = promuteBundle(i18nBundle, l, defaultLocale,
                                        promuteGeneral);
            }
            result.addAll(Arrays.asList(entries));
        }
        return result.toArray(new I18nBundleEntry[result.size()]);
    }

    /**
     * Recherche la liste des url de toutes les resources i18n, i.e les urls des
     * fichiers de traduction.
     *
     * @param urls les urls à inspecter pour trouver des resources i18n
     * @return la liste des urls de bundle i18n
     */
    public static URL[] getURLs(URL... urls) {

        try {
            // on calcule toutes les urls utilisable dans le classloader donnee
            List urlToSeek = new ArrayList();
            urlToSeek.addAll(Arrays.asList(urls));

            // on va maintenant supprimer toutes les urls qui ne respectent pas
            // le pattern i18n : il faut que la resource contienne un
            // repertoire i18n, ce simple test permet de restreindre la
            // recherche des resources
            // i18n qui est tres couteuse
            int size = urlToSeek.size();
            for (Iterator it = urlToSeek.iterator(); it.hasNext();) {
                URL url = it.next();
                if (!I18nUtil.containsDirectDirectory(
                        url, DIRECTORY_SEARCH_BUNDLE_PATTERN)) {
                    if (log.isDebugEnabled()) {
                        log.debug("skip url with no " +
                                  DIRECTORY_SEARCH_BUNDLE_PATTERN +
                                  " directory : " + url);
                    }
                    it.remove();
                }
            }

            if (log.isDebugEnabled()) {
                log.debug("detect " + urlToSeek.size() +
                          " i18n capable url (out of " + size + ")");
            }

            List listURLs = new ArrayList();

            for (URL url : urlToSeek) {
                // on recherche tous les fichiers de traduction pour cet url

                List result = null;

                if (log.isDebugEnabled()) {
                    log.debug("seek in : " + url);
                }

                String fileName = url.getFile();
                // TODO deal with encoding in windows, this is very durty,
                // TODO but it works...
                File file = new File(fileName.replaceAll("%20", " "));
                if (log.isDebugEnabled()) {
                    log.debug("url to search " + file);
                    log.debug("Is a exsting file or directory ? " + file.exists());
                }
                if (I18nUtil.isJar(fileName)) {
                    // cas ou le ichier du classLoader est un fichier jar
                    if (log.isDebugEnabled()) {
                        log.debug("jar to search " + file);
                    }
                    result = getURLsFromJar(url, file);

                } else if (file.isDirectory()) {
                    // cas ou le ichier du classLoader est un repertoire
                    if (log.isDebugEnabled()) {
                        log.debug("directory to search " + file);
                    }
                    // on traite le cas ou il peut y avoir des repertoire
                    // dans ce repertoire
                    result = getURLsFromDirectory(url, file);
                }
                if (result != null && !result.isEmpty()) {
                    listURLs.addAll(result);
                }

            }
            return listURLs.toArray(new URL[listURLs.size()]);
        } catch (Exception eee) {
            if (log.isWarnEnabled()) {
                log.warn("Unable to find urls for urls : " + Arrays.toString(urls) +
                         " for reason " + eee.getMessage(), eee);
            }
            return EMPTY_URL_ARRAY;
        }
    }

    /**
     * Teste si un ensemble de bundles contient au moins une entrée.
     *
     * @param bundles les bundles a parcourir
     * @return {@code true} si aucune entree trouvee, {@code false}
     *         autrement.
     */
    public static boolean isEmpty(I18nBundle... bundles) {
        for (I18nBundle i18nBundle : bundles) {
            if (!i18nBundle.getEntries().isEmpty()) {
                // on a trouve au moins une entree
                return false;
            }
        }
        return true;
    }

    /**
     * Detecte les bundles i18n a partir des urls des fichiers de traduction
     * donnes.
     *
     * Tous les entrées de bundles sont triees dans l'ordre des scopes i18n.
     *
     * @param urls les urls des fichiers de traductions
     * @return la liste des bundle i18n construits à partir des fichiers de
     *         traduction donnes.
     */
    public static List detectBundles(URL... urls) {

        List bundleNames = new ArrayList();
        List bundles = new ArrayList();

        for (URL url : urls) {

            if (addBundleEntry(url, I18nBundleScope.FULL, bundleNames,
                               bundles)) {
                // found a full bundle
                continue;
            }
            if (addBundleEntry(url, I18nBundleScope.LANGUAGE, bundleNames,
                               bundles)) {
                // found a language bundle
                continue;
            }
            // must be a general bundle with no locale defined
            addBundleEntry(url, I18nBundleScope.GENERAL, bundleNames, bundles);
        }
        bundleNames.clear();

        // once for all, sort entries from general to full
        for (I18nBundle bundle : bundles) {
            Collections.sort(bundle.getEntries());
        }

        return bundles;
    }

    protected static boolean addBundleEntry(URL url,
                                            I18nBundleScope scope,
                                            List bundleNames,
                                            List bundles) {
        String path = url.toString();
        Matcher matcher = scope.getMatcher(path);
        if (!matcher.matches()) {
            // no match at this scope
            return false;
        }
        // create a new bundle entry
        I18nBundleEntry entry =
                new I18nBundleEntry(url, scope.getLocale(matcher), scope);
        if (log.isDebugEnabled()) {
            log.debug("bundle (" + bundles.size() + ") : " + entry);
        }
        // get the associated bundle
        I18nBundle bundle =
                addBundle(scope.getBundlePrefix(matcher), bundleNames, bundles);
        // add entry to bundle
        bundle.addEntry(entry);
        return true;
    }

    protected static I18nBundle addBundle(String bundleName,
                                          List bundleNames,
                                          List bundles) {
        I18nBundle bundle;
        int index = bundleNames.indexOf(bundleName);
        if (index > -1) {
            bundle = bundles.get(index);
        } else {
            bundle = new I18nBundle(bundleName);
            if (log.isDebugEnabled()) {
                log.debug("bundle (" + bundles.size() + ") : " + bundle);
            }
            bundles.add(bundle);
            bundleNames.add(bundleName);
        }
        return bundle;
    }

    /**
     * Obtain some rescue entries for a given locale.
     *
     * Note: Calling this method implies there is no entry matched by the
     * common method {@link #getBundleEntries(Locale, Locale, I18nBundle...)}
     * returns a empty array.
     *
     * @param bundle         the bundle to promute
     * @param l              the locale required
     * @param defaultLocale  the default locale to used for promotion
     * @param promuteGeneral a flag to authorize promotion to default locale
     * @return the table of entries promuted for the given locale
     */
    protected static I18nBundleEntry[] promuteBundle(I18nBundle bundle,
                                                     Locale l,
                                                     Locale defaultLocale,
                                                     boolean promuteGeneral) {

        I18nBundleScope scope = I18nBundleScope.valueOf(l);

        if (log.isDebugEnabled()) {
            log.debug('[' + bundle.getBundlePrefix() + "] did not find" +
                      " matching entries for locale " + l +
                      ". Try to detect best entries...");
        }

        if (bundle.size() == 0) {
            // there is no entry to take...
            if (log.isWarnEnabled()) {
                log.warn("PROMUTE NO ENTRY FOUND");
            }
            return EMPTY_I18N_BUNDLE_ENTRYS_ARRAY;
        }

        if (bundle.size() == 1) {
            // there is one entry take it,what ever...
            I18nBundleEntry entry = bundle.getEntries().get(0);
            if (log.isWarnEnabled()) {
                log.warn("PROMUTE" + l + " to " + entry.getLocale() +
                         " [" + bundle.getBundlePrefix() + ']');
            }
            return new I18nBundleEntry[]{entry};
        }

        List result = new ArrayList();

        switch (scope) {
            case FULL:
                promuteFull(bundle, l, defaultLocale, result, promuteGeneral);
                break;
            case LANGUAGE:
                promuteLanguage(bundle, l, defaultLocale, result,
                                promuteGeneral);
                break;
            case GENERAL:
                if (promuteGeneral) {
                    promuteGeneral(bundle, l, defaultLocale, result);
                }
                break;
        }
        return result.toArray(new I18nBundleEntry[result.size()]);
    }

    protected static void promuteFull(I18nBundle bundle,
                                      Locale locale,
                                      Locale defaultLocale,
                                      List result,
                                      boolean promuteGeneral) {
        if (bundle.size() == 0) {
            return;
        }
        // try with a another FULL matching locale ?
        for (I18nBundleEntry entry : bundle.getEntries()) {
            I18nBundleScope i18nBundleScope = entry.getScope();
            // load from general to the max scope and always if there is only
            // one bundle entry found
            Locale locale1 = entry.getLocale();
            if (i18nBundleScope == I18nBundleScope.FULL &&
                !locale1.getCountry().equals(locale.getCountry()) &&
                locale1.getLanguage().equals(locale.getLanguage())) {
                if (log.isWarnEnabled()) {
                    log.warn(locale + " to " + locale1 +
                             " [" + bundle.getBundlePrefix() + ']');
                }
                result.add(entry);
                // we take the first one, this is a resuce!!!
                break;
            }
        }
        if (result.isEmpty()) {
            // full promotion failed,trylanguage promotion
            promuteLanguage(bundle, locale, defaultLocale, result,
                            promuteGeneral);
        }

    }

    protected static void promuteLanguage(I18nBundle bundle,
                                          Locale locale,
                                          Locale defaultLocale,
                                          List result,
                                          boolean promuteGeneral) {
        if (bundle.size() == 0) {
            return;
        }
        for (I18nBundleEntry entry : bundle.getEntries()) {
            I18nBundleScope i18nBundleScope = entry.getScope();
            // load from general to the max scope and always if there is only
            //  one bundle entry found
            Locale locale1 = entry.getLocale();
            if (i18nBundleScope == I18nBundleScope.FULL &&
                locale1.getLanguage().equals(locale.getLanguage())) {
                result.add(entry);
                if (log.isWarnEnabled()) {
                    log.warn(locale + " to " + locale1 + " [" +
                             bundle.getBundlePrefix() + ']');
                }
                // we take the first one, this is a resuce!!!
                break;
            }
        }
        if (result.isEmpty() && promuteGeneral) {
            // language promotion failed,try general promotion
            promuteGeneral(bundle, locale, defaultLocale, result);
        }
    }

    protected static void promuteGeneral(I18nBundle bundle,
                                         Locale locale,
                                         Locale defaultLocale,
                                         List result) {
        if (bundle.size() == 0) {
            return;
        }
        if (bundle.size() == 1) {
            // there is one entry take it,what ever...
            I18nBundleEntry entry = bundle.getEntries().get(0);
            result.add(entry);
            if (log.isWarnEnabled()) {
                log.warn(locale + " to " + entry.getLocale() + " [" +
                         bundle.getBundlePrefix() + ']');
            }
            return;
        }
        I18nBundleScope scope = I18nBundleScope.valueOf(defaultLocale);
        for (I18nBundleEntry entry : bundle.getEntries(scope)) {
            if (entry.getLocale().equals(defaultLocale)) {
                // default locale found
                if (log.isWarnEnabled()) {
                    log.warn(locale + " to " + entry.getLocale() + " [" +
                             bundle.getBundlePrefix() + ']');
                }
                result.add(entry);
                return;
            }
        }

        // default locale not found, take the first one ?
        I18nBundleEntry entry = bundle.getEntries().get(0);
        result.add(entry);
        if (log.isWarnEnabled()) {
            log.warn(locale + " to " + entry.getLocale() + " [" +
                     bundle.getBundlePrefix() + ']');
        }
        //TODO Should try to load default en_GB from I18nLoader ?
        //I18n.DEFAULT_LOCALE.getCountry()
    }

    protected static List getURLsFromJar(URL incomingURL, File jarfile) {


        /**
         * fix ano-1812
         * use this fixed pattern instead of getSearchBundlePattern() because ZipEntry.getName() always use 'linux' path description
         */
        String pattern = ".*i18n/.+\\.properties";
        
        try {
            ZipInputStream zis = new ZipInputStream(new FileInputStream(jarfile));
            try {
                List result = new ArrayList();
                ClassLoader cl = new URLClassLoader(
                        new URL[]{incomingURL},
                        I18nBundleUtil.class.getClassLoader());
                while (zis.available() != 0) {
                    ZipEntry entry = zis.getNextEntry();

                    if (entry == null) {
                        break;
                    }

                    String name = entry.getName();

                    if (name.matches(pattern)) {
                        // on recupere le fichier correspondant au pattern dans le
                        // classloader
                        if (log.isDebugEnabled()) {
                            log.debug(name + " accepted for pattern " + pattern);
                        }
                        URL url = cl.getResource(name);
                        // on ajoute le fichier correspondant au pattern dans la
                        // liste
                        result.add(url);
                    }
                }

                return result;
            } finally {
                zis.close();
            }
        } catch (Exception eee) {
            throw new RuntimeException(
                    "n'a pas pu trouve la resource dans le jar " +
                    jarfile.getAbsolutePath(), eee);
        }
    }

    /**
     * Compute the search pattern according to the {@link File#separator} on
     * the underlying os.
     *
     * Under linux this is {@code .*i18n/.+\.properties}, and under windows this
     * is {@code .*i18n\\\\.+\.properties}.
     *
     * @return the correct pattern
     * @since 2.0
     */
    protected static String getSearchBundlePattern() {

        String result = ".*i18n";
        String path = File.separator;
        result += "\\".equals(path) ? path + path : path;
        return result + ".+\\.properties";
    }

    protected static List getURLsFromDirectory(URL incomingURL,
                                                    File repository) {
        String pattern = getSearchBundlePattern();
        try {
            if (log.isDebugEnabled()) {
                log.debug("search '" + pattern + "' in " + repository);
            }

            List urlList = new ArrayList();
            File[] filesList = repository.listFiles();

            if (filesList != null) {

                for (File file : filesList) {

                    String name = file.getAbsolutePath();

                    // cas de recursivite : repertoire dans un repertoire
                    if (file.exists() && file.isDirectory()) {
                        urlList.addAll(I18nUtil.getURLsFromDirectory(file,
                                                                     pattern));
                        // si le fichier du repertoire n'est pas un repertoire
                        // on verifie s'il correspond au pattern
                    } else if (name.matches(pattern)) {
                        URL url = file.toURI().toURL();
                        if (log.isDebugEnabled()) {
                            log.debug("directory: " + repository + " url: " +
                                      url);
                        }
                        urlList.add(url);
                    }
                }
            }
            return urlList;
        } catch (MalformedURLException eee) {
            throw new RuntimeException(
                    "n'a pas pu trouve la resource dans le repertoire " +
                    repository.getAbsolutePath(), eee);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy