org.lanternpowered.server.text.translation.LanternTranslationManager.java Source code

Java tutorial

Introduction

Here is the source code for org.lanternpowered.server.text.translation.LanternTranslationManager.java

Source

/*
 * This file is part of LanternServer, licensed under the MIT License (MIT).
 *
 * Copyright (c) LanternPowered <https://www.lanternpowered.org>
 * Copyright (c) SpongePowered <https://www.spongepowered.org>
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the Software), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.lanternpowered.server.text.translation;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.lanternpowered.server.util.Conditions.checkNotNullOrEmpty;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.Sets;
import org.lanternpowered.api.asset.Asset;
import org.lanternpowered.server.asset.ReloadListener;
import org.lanternpowered.server.game.Lantern;
import org.spongepowered.api.text.translation.ResourceBundleTranslation;
import org.spongepowered.api.text.translation.Translation;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.Nullable;

public final class LanternTranslationManager implements TranslationManager, ReloadListener {

    private static class ResourceKey {

        private final String name;
        @Nullable
        private final Locale locale;

        ResourceKey(String name, @Nullable Locale locale) {
            this.name = name;
            this.locale = locale;
        }

        @Override
        public int hashCode() {
            return 31 * this.name.hashCode() + (this.locale == null ? 0 : this.locale.hashCode());
        }
    }

    private final LoadingCache<ResourceKey, Optional<ResourceBundle>> resourceBundlesCache = Caffeine.newBuilder()
            .build(key -> {
                final Locale locale = key.locale == null ? Locale.ENGLISH : key.locale;
                Optional<ResourceBundle> optBundle = this.load(key.name, locale);
                if (!optBundle.isPresent() && locale != Locale.ENGLISH) {
                    optBundle = this.load(key.name, Locale.ENGLISH);
                }
                return optBundle;
            });

    private Optional<ResourceBundle> load(String name, Locale locale) throws Exception {
        if (this.bundles.containsKey(locale)) {
            for (ResourceBundle resourceBundle : this.bundles.get(locale)) {
                if (resourceBundle.containsKey(name)) {
                    return Optional.of(resourceBundle);
                }
            }
        }
        return Optional.empty();
    }

    private final ConcurrentMap<Locale, Set<ResourceBundle>> bundles = new ConcurrentHashMap<>();
    private final Map<Asset, Locale> entries = new HashMap<>();

    @Override
    public void addResourceBundle(Asset asset, Locale locale) {
        checkNotNull(asset, "asset");
        checkNotNull(locale, "locale");
        synchronized (this.entries) {
            checkArgument(!this.entries.containsKey(asset),
                    "The asset %s is already added to this translation manager.", asset.getId());
            this.entries.put(asset, locale);
            this.loadAssetBundle(asset, locale, true);
        }
    }

    private void loadAssetBundle(Asset asset, Locale locale, boolean refresh) {
        try {
            final InputStream inputStream = asset.getUrl().openStream();
            try {
                final ResourceBundle bundle = new PropertyResourceBundle(inputStream);
                this.bundles.computeIfAbsent(locale, locale0 -> Sets.newConcurrentHashSet()).add(bundle);
                if (refresh) {
                    final Set<ResourceKey> refreshKeys = Sets.newHashSet();
                    for (ResourceKey key : this.resourceBundlesCache.asMap().keySet()) {
                        Locale locale1 = key.locale == null ? Locale.ENGLISH : key.locale;
                        if (locale1.equals(locale) && bundle.containsKey(key.name)) {
                            refreshKeys.add(key);
                        }
                    }
                    if (!refreshKeys.isEmpty()) {
                        this.resourceBundlesCache.invalidateAll(refreshKeys);
                    }
                }
            } catch (IOException e) {
                Lantern.getLogger().warn("Unable to create the resource bundle for: " + asset.getId(), e);
            }
        } catch (IOException e) {
            Lantern.getLogger().warn("Unable to open the asset stream for: " + asset.getId(), e);
        }
    }

    @Override
    public Translation get(final String key) {
        return new ResourceBundleTranslation(checkNotNullOrEmpty(key, "key"),
                locale -> this.resourceBundlesCache.get(new ResourceKey(key, locale)).orElse(null));
    }

    @Override
    public Optional<Translation> getIfPresent(String key) {
        checkNotNullOrEmpty(key, "key");
        if (this.resourceBundlesCache.get(new ResourceKey(key, null)).isPresent()) {
            return Optional.of(this.get(key));
        }
        return Optional.empty();
    }

    @Override
    public void onReload() {
        synchronized (this.entries) {
            this.bundles.clear();
            this.resourceBundlesCache.invalidateAll();
            for (Map.Entry<Asset, Locale> entry : this.entries.entrySet()) {
                this.loadAssetBundle(entry.getKey(), entry.getValue(), false);
            }
        }
    }
}