org.lunifera.ecview.xtext.builder.participant.impl.I18nRegistry.java Source code

Java tutorial

Introduction

Here is the source code for org.lunifera.ecview.xtext.builder.participant.impl.I18nRegistry.java

Source

/**
 * Copyright (c) 2011 - 2015, Lunifera GmbH (Gross Enzersdorf), Loetz KG (Heidelberg)
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *         Florian Pirchner - Initial implementation
 */
package org.lunifera.ecview.xtext.builder.participant.impl;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.map.LRUMap;
import org.eclipse.core.runtime.FileLocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Singleton;

@Singleton
public class I18nRegistry {

    private static final Logger LOGGER = LoggerFactory.getLogger(I18nRegistry.class);

    private final LRUMap fastAccess = new LRUMap(1000);

    private final Map<Locale, List<ResourceDescription>> cache = new HashMap<Locale, List<ResourceDescription>>();

    public I18nRegistry() {

    }

    /**
     * Tries to find the translation for the given locale and key.
     * 
     * @param locale
     * @param key
     * @return
     */
    public String findTranslation(Locale locale, String key) {
        String result = tryFastAccess(locale, key);
        if (result != null) {
            return result;
        }

        AccessPath accessPath = computeBestMatchAccessPath(locale, key);
        Translation translation = accessPath.getTranslation();
        if (translation == null) {
            return "";
        }

        return putFastAccess(translation.getLocale(), translation.getI18nKey(), translation.getI18nValue());
    }

    /**
     * Try to find the translation in the fast access map.
     * 
     * @param locale
     * @param key
     * @return
     */
    private String tryFastAccess(Locale locale, String key) {
        List<Locale> computedLocales = computeLocales(locale);
        for (Locale temp : computedLocales) {
            String fastAccessKey = createFastAccessKey(temp, key);
            String result = (String) fastAccess.get(fastAccessKey);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    /**
     * Try to find the translation in the fast access map.
     * 
     * @param locale
     * @param key
     * @return
     */
    private String putFastAccess(Locale locale, String key, String translation) {
        fastAccess.put(createFastAccessKey(locale, key), translation);
        return translation;
    }

    /**
     * Creates the key for the fast access map access.
     * 
     * @param temp
     * @param key
     * @return
     */
    private String createFastAccessKey(Locale temp, String key) {
        return temp.toLanguageTag() + ":" + key;
    }

    /**
     * Computes the access url.
     * <p>
     * Following order will be used:
     * <ol>
     * <li>Use given locale</li>
     * <li>Create more specific locale and repeat until default locale is
     * reached.</li>
     * </ul> </li>
     * </ol>
     * 
     * @param locale
     * @param packageName
     * @param key
     * @return
     */
    private AccessPath computeAccessPath(Locale locale, String key) {

        Matcher valueMatcher = null;
        if (key != null && !key.equals("")) {
            key = Pattern.quote(key);
            Pattern valuePattern = Pattern.compile(key.toString());
            valueMatcher = valuePattern.matcher("");
            valueMatcher.reset();
        }

        AccessPath url = new AccessPath();
        int prio = 0;

        List<Locale> computedLocales = computeLocales(locale);

        for (Locale computedLocale : computedLocales) {
            Accessor accessor = new Accessor(computedLocale, ++prio, key, valueMatcher);
            url.addAccessor(accessor);
        }

        return url;
    }

    /**
     * Computes the access url to find the best matching element. See
     * {@link #computeAccessPath(IProject, Locale, String, String)}.
     *
     * @param locale
     * @param packageName
     * @param key
     * @return
     */
    private AccessPath computeBestMatchAccessPath(Locale locale, String key) {
        return computeAccessPath(locale, key);
    }

    /**
     * Computes all locales that should be added to AccessPath
     * 
     * @param locale
     * @return
     */
    private List<Locale> computeLocales(Locale locale) {
        List<Locale> locales = new LinkedList<Locale>();

        // Add first locale
        locales.add(locale);

        Locale temp = locale;
        while (true) {
            String tag = temp.toLanguageTag();
            String[] segments = tag.split("-");
            if (segments.length > 1) {
                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < segments.length - 1; i++) {
                    if (builder.length() != 0) {
                        builder.append("-");
                    }
                    builder.append(segments[i]);
                }
                Locale moreGeneral = Locale.forLanguageTag(builder.toString());
                locales.add(moreGeneral);
                temp = moreGeneral;
            } else {
                break;
            }
        }

        locales.add(new Locale(""));

        return locales;
    }

    private boolean isValid(String result) {
        return result != null && !result.equals("");
    }

    /**
     * Add a new resource to the cache.
     * 
     * @param description
     */
    public void addResource(URL url) {

        Properties properties = new Properties();
        try {
            properties.load(url.openStream());
        } catch (IOException e) {
            LOGGER.error("{}", e);
            return;
        }

        Locale locale = getLocale(url);
        if (locale == null) {
            LOGGER.error("Could not determine locale for " + url.toString());
            return;
        }

        ResourceDescription description = new ResourceDescription(locale, url, properties);
        addResource(description);
    }

    private Locale getLocale(URL url) {
        File file = null;
        try {
            file = new File(FileLocator.toFileURL(url).toURI());
        } catch (URISyntaxException e) {
            LOGGER.error("{}", e);
            return null;
        } catch (IOException e) {
            LOGGER.error("{}", e);
            return null;
        }

        String[] tokens = file.getName().replace(".properties", "").split("_");
        StringBuilder builder = new StringBuilder();
        if (tokens.length >= 2) {
            for (int i = 1; i < tokens.length; i++) {
                if (builder.length() > 0) {
                    builder.append("-");
                }
                builder.append(tokens[i]);
            }
        }
        Locale locale = Locale.forLanguageTag(builder.toString());
        return locale;
    }

    /**
     * Add a new resource to the cache.
     * 
     * @param description
     */
    public void addResource(ResourceDescription description) {
        synchronized (cache) {
            if (!cache.containsKey(description.getLocale())) {
                cache.put(description.getLocale(), new ArrayList<ResourceDescription>());
            }

            List<ResourceDescription> descs = cache.get(description.getLocale());
            if (!descs.contains(description)) {
                descs.add(description);
            }
        }

        // clear the fast access cache
        fastAccess.clear();
    }

    private List<ResourceDescription> getResourceDescriptions(Locale locale) {
        if (cache.containsKey(locale)) {
            return new ArrayList<I18nRegistry.ResourceDescription>(cache.get(locale));
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Removes the given translations.
     * 
     * @param locale
     * @param url
     */
    public void removeResource(Locale locale, URL url) {
        if (!cache.containsKey(locale)) {
            return;
        }

        // clear the fast access cache
        fastAccess.clear();

        // remove the resource from the builder
        synchronized (cache) {
            List<ResourceDescription> descs = cache.get(locale);
            for (Iterator<ResourceDescription> iterator = descs.iterator(); iterator.hasNext();) {
                ResourceDescription desc = iterator.next();
                if (desc.getURL().equals(url)) {
                    iterator.remove();
                }
            }
        }
    }

    /**
     * Removes the translations with the given URL.
     * 
     * @param url
     */
    public void removeResource(URL url) {
        Locale locale = getLocale(url);
        if (locale == null) {
            LOGGER.error("Could not determine locale for " + url.toString());
            return;
        }
        removeResource(locale, url);

    }

    /**
     * Defines how the registry should be searched. For instance the ordering of
     * locales...
     */
    private static class AccessPath {
        private List<Accessor> accessors = new LinkedList<I18nRegistry.Accessor>();

        public AccessPath() {
        }

        public void addAccessor(Accessor accessor) {
            accessors.add(accessor);
        }

        /**
         * Returns the translation. The key matches the entire key for an i18n
         * record.
         * 
         * @return
         */
        public Translation getTranslation() {
            for (Accessor accessor : accessors) {
                Translation result = accessor.getTranslation();
                if (result != null) {
                    return result;
                }
            }

            return null;
        }
    }

    /**
     * This class will access the registry.
     */
    private class Accessor {

        private final Locale locale;
        private final Matcher matcher;
        @SuppressWarnings("unused")
        private final String key;
        private final int prio;

        public Accessor(Locale locale, int prio, String key, Matcher matcher) {
            super();
            this.locale = locale;
            this.key = key;
            this.matcher = matcher;
            this.prio = prio;
        }

        /**
         * Returns the first translation found. The matcher must match the given
         * key.
         * 
         * @return
         */
        public Translation getTranslation() {
            List<ResourceDescription> descs = getResourceDescriptions(locale);
            for (ResourceDescription desc : descs) {
                for (Map.Entry<Object, Object> entry : desc.getProperties().entrySet()) {
                    if (matcher.reset(((String) entry.getKey())).matches()) {
                        if (isValid((String) entry.getValue())) {
                            return new Translation((String) entry.getKey(), (String) entry.getValue(), desc, prio);
                        }
                    }
                }
            }
            return null;
        }
    }

    /**
     * A resource is a file containing all translations for a file.
     */
    public static class ResourceDescription {
        private final Locale locale;
        private final URL url;
        private final Properties properties;

        public ResourceDescription(Locale locale, URL url, Properties properties) {
            this.locale = locale;
            this.url = url;
            this.properties = properties;
        }

        public Locale getLocale() {
            return locale;
        }

        public URL getURL() {
            return url;
        }

        public Properties getProperties() {
            return properties;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((url == null) ? 0 : url.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ResourceDescription other = (ResourceDescription) obj;
            if (url == null) {
                if (other.url != null)
                    return false;
            } else if (!url.equals(other.url))
                return false;
            return true;
        }
    }

    public static class Translation {

        private final String i18nKey;
        private final String i18nValue;
        private final ResourceDescription resourceDescription;
        @SuppressWarnings("unused")
        private final int priority;

        public Translation(String i18nKey, String i18nValue, ResourceDescription resourceDescription,
                int priority) {
            super();
            this.i18nKey = i18nKey;
            this.i18nValue = i18nValue;
            this.resourceDescription = resourceDescription;
            this.priority = priority;
        }

        /**
         * Returns the i18nKey.
         * 
         * @return
         */
        public String getI18nKey() {
            return i18nKey;
        }

        /**
         * Returns the i18nValue.
         * 
         * @return
         */
        public String getI18nValue() {
            return i18nValue;
        }

        /**
         * Returns the locale where key and value had been found.
         * 
         * @return
         */
        public Locale getLocale() {
            return resourceDescription.getLocale();
        }

        /**
         * Returns the resource description, where the i18n entry was contained.
         * 
         * @return
         */
        public ResourceDescription getResourceDescription() {
            return resourceDescription;
        }

    }
}