/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* 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 com.jcwhatever.nucleus.internal.providers;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.internal.providers.bankitems.BankItemsProvider;
import com.jcwhatever.nucleus.internal.providers.economy.NucleusEconomyProvider;
import com.jcwhatever.nucleus.internal.providers.economy.VaultEconomyBankProvider;
import com.jcwhatever.nucleus.internal.providers.economy.VaultEconomyProvider;
import com.jcwhatever.nucleus.internal.providers.friends.NucleusFriendsProvider;
import com.jcwhatever.nucleus.internal.providers.jail.NucleusJailProvider;
import com.jcwhatever.nucleus.internal.providers.kits.NucleusKitProvider;
import com.jcwhatever.nucleus.internal.providers.math.NucleusFastMathProvider;
import com.jcwhatever.nucleus.internal.providers.permissions.bukkit.BukkitProvider;
import com.jcwhatever.nucleus.internal.providers.permissions.vault.VaultProvider;
import com.jcwhatever.nucleus.internal.providers.selection.NucleusSelectionProvider;
import com.jcwhatever.nucleus.internal.providers.selection.WorldEditSelectionProvider;
import com.jcwhatever.nucleus.internal.providers.storage.JsonStorageProvider;
import com.jcwhatever.nucleus.internal.providers.storage.YamlStorageProvider;
import com.jcwhatever.nucleus.mixins.IDisposable;
import com.jcwhatever.nucleus.providers.IProvider;
import com.jcwhatever.nucleus.providers.IProviderManager;
import com.jcwhatever.nucleus.providers.ProviderType;
import com.jcwhatever.nucleus.providers.bankitems.IBankItemsProvider;
import com.jcwhatever.nucleus.providers.economy.IEconomyProvider;
import com.jcwhatever.nucleus.providers.friends.IFriendsProvider;
import com.jcwhatever.nucleus.providers.jail.IJailProvider;
import com.jcwhatever.nucleus.providers.kits.IKitProvider;
import com.jcwhatever.nucleus.providers.math.IFastMathProvider;
import com.jcwhatever.nucleus.providers.npc.INpcProvider;
import com.jcwhatever.nucleus.providers.permissions.IPermissionsProvider;
import com.jcwhatever.nucleus.providers.playerlookup.IPlayerLookupProvider;
import com.jcwhatever.nucleus.providers.regionselect.IRegionSelectProvider;
import com.jcwhatever.nucleus.providers.sql.ISqlProvider;
import com.jcwhatever.nucleus.providers.storage.IStorageProvider;
import com.jcwhatever.nucleus.storage.DataPath;
import com.jcwhatever.nucleus.storage.IDataNode;
import com.jcwhatever.nucleus.storage.MemoryDataNode;
import com.jcwhatever.nucleus.storage.YamlDataNode;
import com.jcwhatever.nucleus.utils.DependencyRunner;
import com.jcwhatever.nucleus.utils.DependencyRunner.DependencyStatus;
import com.jcwhatever.nucleus.utils.DependencyRunner.IDependantRunnable;
import com.jcwhatever.nucleus.utils.DependencyRunner.IFinishHandler;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.observer.future.FutureAgent;
import com.jcwhatever.nucleus.utils.observer.future.IFuture;
import org.bukkit.plugin.Plugin;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Internal provider manager implementation.
*/
public final class InternalProviderManager implements IProviderManager {
private IDataNode _dataNode;
// names of all providers that were found
private Map<String, ProviderInfo> _providerInfo = new HashMap<>(25);
private Multimap<ProviderType, String> _providerNamesByApi =
MultimapBuilder.enumKeys(ProviderType.class).hashSetValues().build();
// providers that need to be enabled
private Set<IProvider> _toEnable = new HashSet<>(25);
private volatile IPlayerLookupProvider _playerLookup;
private volatile IFriendsProvider _friends;
private volatile IPermissionsProvider _permissions;
private volatile IRegionSelectProvider _regionSelect;
private volatile IEconomyProvider _economy;
private volatile IBankItemsProvider _bankItems;
private volatile IFastMathProvider _math;
private volatile INpcProvider _npc;
private volatile IJailProvider _jail;
private volatile IStorageProvider _defaultStorage;
private volatile IKitProvider _kits;
private volatile ISqlProvider _sql;
private final YamlStorageProvider _yamlStorage = new YamlStorageProvider();
// keyed to plugin name
private final Map<String, IStorageProvider> _pluginStorage = new HashMap<>(35);
// keyed to provider name
private final Map<String, IStorageProvider> _storageProviders = new HashMap<>(10);
private boolean _isProvidersLoading;
public InternalProviderManager(boolean isTest) {
_storageProviders.put(_yamlStorage.getInfo().getSearchName(), _yamlStorage);
_defaultStorage = _yamlStorage;
// register names of default providers
addName(BankItemsProvider.NAME, ProviderType.BANK_ITEMS);
addName(NucleusEconomyProvider.NAME, ProviderType.ECONOMY);
addName(VaultEconomyProvider.NAME, ProviderType.ECONOMY);
addName(NucleusFriendsProvider.NAME, ProviderType.FRIENDS);
addName(NucleusJailProvider.NAME, ProviderType.JAIL);
addName(BukkitProvider.NAME, ProviderType.PERMISSIONS);
addName(VaultProvider.NAME, ProviderType.PERMISSIONS);
addName(NucleusSelectionProvider.NAME, ProviderType.REGION_SELECT);
addName(WorldEditSelectionProvider.NAME, ProviderType.REGION_SELECT);
addName(YamlStorageProvider.NAME, ProviderType.STORAGE);
addName(JsonStorageProvider.NAME, ProviderType.STORAGE);
_dataNode = isTest
? new MemoryDataNode(Nucleus.getPlugin())
: new YamlDataNode(Nucleus.getPlugin(), new DataPath("providers"));
_dataNode.load();
registerStorageProvider(new JsonStorageProvider());
// setup preferred internal bank items
String prefBankItems = getPreferred(ProviderType.BANK_ITEMS);
if (BankItemsProvider.NAME.equalsIgnoreCase(prefBankItems) || prefBankItems == null) {
_bankItems = add(new BankItemsProvider());
}
// setup preferred internal economy
String prefEcon = getPreferred(ProviderType.ECONOMY);
if (NucleusEconomyProvider.NAME.equalsIgnoreCase(prefEcon) || prefEcon == null) {
_economy = add(new NucleusEconomyProvider(Nucleus.getPlugin()));
}
else if (VaultEconomyProvider.NAME.equalsIgnoreCase(prefEcon) &&
VaultEconomyProvider.hasVaultEconomy()) {
_economy = add(VaultEconomyBankProvider.hasBankEconomy()
? new VaultEconomyBankProvider()
: new VaultEconomyProvider());
}
String prefMath = getPreferred(ProviderType.MATH);
if (NucleusFastMathProvider.NAME.equalsIgnoreCase(prefMath) || prefMath == null) {
_math = add(new NucleusFastMathProvider());
}
// setup preferred internal friends
String prefFriends = getPreferred(ProviderType.FRIENDS);
if (NucleusFriendsProvider.NAME.equalsIgnoreCase(prefFriends)) {
_friends = add(new NucleusFriendsProvider());
}
// setup preferred internal jail
String prefJail = getPreferred(ProviderType.JAIL);
if (NucleusJailProvider.NAME.equalsIgnoreCase(prefJail)) {
_jail = add(new NucleusJailProvider());
}
String prefKit = getPreferred(ProviderType.KITS);
if (NucleusKitProvider.NAME.equalsIgnoreCase(prefKit)) {
_kits = add(new NucleusKitProvider());
}
// setup preferred internal permissions
String prefPerm = getPreferred(ProviderType.PERMISSIONS);
if ((VaultProvider.NAME.equalsIgnoreCase(prefPerm)) &&
VaultProvider.hasVaultPermissions()) {
_permissions = add(VaultProvider.getVaultProvider());
}
else if (BukkitProvider.NAME.equalsIgnoreCase(prefPerm)) {
_permissions = add(new BukkitProvider());
}
// setup preferred internal region selection
String prefSelect = getPreferred(ProviderType.REGION_SELECT);
if (NucleusSelectionProvider.NAME.equalsIgnoreCase(prefSelect) &&
!WorldEditSelectionProvider.isWorldEditInstalled()) {
_regionSelect = add(new NucleusSelectionProvider());
}
else if (WorldEditSelectionProvider.NAME.equalsIgnoreCase(prefSelect) &&
WorldEditSelectionProvider.isWorldEditInstalled()) {
_regionSelect = add(new WorldEditSelectionProvider());
}
}
/**
* Enable all providers.
*/
public IFuture enableProviders() {
final FutureAgent agent = new FutureAgent();
DependencyRunner<IDependantRunnable> runner =
new DependencyRunner<IDependantRunnable>(Nucleus.getPlugin());
Iterator<IProvider> iterator = _toEnable.iterator();
while (iterator.hasNext()) {
IProvider provider = iterator.next();
try {
provider.registerTypes();
}
catch (Throwable e) {
e.printStackTrace();
iterator.remove();
}
}
for (final IProvider provider : _toEnable) {
try {
provider.enable();
}
catch (Throwable e) {
e.printStackTrace();
continue;
}
runner.add(new IDependantRunnable() {
@Override
public DependencyStatus getDependencyStatus() {
return provider.isLoaded()
? DependencyStatus.READY
: DependencyStatus.NOT_READY;
}
@Override
public void run() {
// do nothing
}
});
}
runner.onFinish(new IFinishHandler<IDependantRunnable>() {
@Override
public void onFinish(List<IDependantRunnable> notRun) {
agent.success();
}
});
_toEnable.clear();
runner.start();
return agent.getFuture();
}
/**
* Add a provider.
*
* @param provider The provider to add.
*
* @return True if the provider was added. Otherwise false.
*/
public boolean addProvider(IProvider provider) {
PreCon.notNull(provider);
PreCon.isValid(_isProvidersLoading, "Cannot set providers outside of provider load time.");
boolean isAdded = false;
if (provider instanceof IPlayerLookupProvider) {
addName(provider, ProviderType.PLAYER_LOOKUP);
if (remove(_playerLookup, ProviderType.PLAYER_LOOKUP)) {
_playerLookup = add(provider);
isAdded = true;
}
}
if (provider instanceof IFriendsProvider) {
addName(provider, ProviderType.FRIENDS);
if (remove(_friends, ProviderType.FRIENDS)) {
_friends = add(provider);
isAdded = true;
}
}
if (provider instanceof IPermissionsProvider) {
addName(provider, ProviderType.PERMISSIONS);
if (remove(_permissions, ProviderType.PERMISSIONS)) {
_permissions = add(provider);
isAdded = true;
}
}
if (provider instanceof IRegionSelectProvider) {
addName(provider, ProviderType.REGION_SELECT);
if (remove(_regionSelect, ProviderType.REGION_SELECT)) {
_regionSelect = add(provider);
isAdded = true;
}
}
if (provider instanceof IBankItemsProvider) {
addName(provider, ProviderType.BANK_ITEMS);
if (remove(_bankItems, ProviderType.BANK_ITEMS)) {
_bankItems = add(_bankItems);
isAdded = true;
}
}
if (provider instanceof IEconomyProvider) {
addName(provider, ProviderType.ECONOMY);
if (remove(_economy, ProviderType.ECONOMY)) {
add(_economy);
_economy = (IEconomyProvider)provider;
isAdded = true;
}
}
if (provider instanceof IStorageProvider) {
addName(provider, ProviderType.STORAGE);
if (remove(_defaultStorage, ProviderType.STORAGE)) {
_defaultStorage = add(provider);
}
registerStorageProvider((IStorageProvider) provider);
isAdded = true;
}
if (provider instanceof INpcProvider) {
addName(provider, ProviderType.NPC);
if (remove(_npc, ProviderType.NPC)) {
_npc = add(provider);
isAdded = true;
}
}
if (provider instanceof IJailProvider) {
addName(provider, ProviderType.JAIL);
if (remove(_jail, ProviderType.JAIL)) {
_jail = add(provider);
isAdded = true;
}
}
if (provider instanceof IKitProvider) {
addName(provider, ProviderType.KITS);
if (remove(_kits, ProviderType.KITS)) {
_kits = add(provider);
isAdded = true;
}
}
if (provider instanceof ISqlProvider) {
addName(provider, ProviderType.SQL);
if (remove(_sql, ProviderType.SQL)) {
_sql = add(provider);
isAdded = true;
}
}
return isAdded;
}
@Override
public boolean setPreferred(ProviderType providerType, @Nullable String providerName) {
PreCon.notNull(providerType);
_dataNode.set("preferred." + providerType.getName(), providerName);
_dataNode.save();
return true;
}
@Nullable
@Override
public String getPreferred(ProviderType providerType) {
PreCon.notNull(providerType);
return _dataNode.getString("preferred." + providerType.getName());
}
@Override
public <T extends IProvider> T get(ProviderType providerType) {
PreCon.notNull(providerType);
@SuppressWarnings("unchecked")
Class<T> apiType = (Class<T>)providerType.getApiType();
IProvider provider;
switch (providerType) {
case BANK_ITEMS:
provider = _bankItems;
break;
case ECONOMY:
provider = _economy;
break;
case FRIENDS:
provider = _friends;
break;
case JAIL:
provider = _jail;
break;
case KITS:
provider = _kits;
break;
case MATH:
provider = _math;
break;
case NPC:
provider = _npc;
break;
case PERMISSIONS:
provider = _permissions;
break;
case PLAYER_LOOKUP:
provider = _playerLookup;
break;
case REGION_SELECT:
provider = _regionSelect;
break;
case STORAGE:
provider = _defaultStorage;
break;
case SQL:
provider = _sql;
break;
default:
throw new AssertionError();
}
if (provider == null)
return null;
return apiType.cast(provider);
}
@Nullable
@Override
public ProviderType getType(String name) {
PreCon.notNull(name);
ProviderInfo info = _providerInfo.get(name.toLowerCase());
if (info == null)
return null;
return info.type;
}
@Override
public IPlayerLookupProvider getPlayerLookup() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_playerLookup == null) {
_playerLookup = new InternalPlayerLookupProvider(Nucleus.getPlugin());
_playerLookup.registerTypes();
_playerLookup.enable();
}
return _playerLookup;
}
@Override
public IFriendsProvider getFriends() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_friends == null) {
_friends = new NucleusFriendsProvider();
_friends.registerTypes();
_friends.enable();
}
return _friends;
}
@Override
public IPermissionsProvider getPermissions() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_permissions == null) {
_permissions = VaultProvider.hasVaultPermissions()
? VaultProvider.getVaultProvider()
: new BukkitProvider();
_permissions.registerTypes();
_permissions.enable();
}
return _permissions;
}
@Override
public IRegionSelectProvider getRegionSelection() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_regionSelect == null) {
_regionSelect = new NucleusSelectionProvider();
_regionSelect.registerTypes();
_regionSelect.enable();
}
return _regionSelect;
}
@Override
public IBankItemsProvider getBankItems() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_bankItems == null) {
_bankItems = new BankItemsProvider();
_bankItems.registerTypes();
_bankItems.enable();
}
return _bankItems;
}
@Override
public IEconomyProvider getEconomy() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_economy == null) {
_economy = VaultEconomyProvider.hasVaultEconomy()
? VaultEconomyBankProvider.hasBankEconomy()
? new VaultEconomyBankProvider()
: new VaultEconomyProvider()
: new NucleusEconomyProvider(Nucleus.getPlugin());
_economy.registerTypes();
_economy.enable();
}
return _economy;
}
@Nullable
@Override
public ISqlProvider getSql() {
return _sql;
}
@Nullable
@Override
public INpcProvider getNpcs() {
return _npc;
}
@Override
public IJailProvider getJails() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_jail == null) {
_jail = new NucleusJailProvider();
_jail.registerTypes();
_jail.enable();
}
return _jail;
}
@Override
public IKitProvider getKits() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_kits == null) {
_kits = new NucleusKitProvider();
_kits.registerTypes();
_kits.enable();
}
return _kits;
}
@Override
public IStorageProvider getStorage() {
return _defaultStorage != null ? _defaultStorage : _yamlStorage;
}
@Override
public IStorageProvider getStorage(Plugin plugin) {
PreCon.notNull(plugin);
synchronized (_pluginStorage) {
IStorageProvider pluginProvider = _pluginStorage.get(plugin.getName().toLowerCase());
return pluginProvider != null ? pluginProvider : getStorage();
}
}
@Override
public void setStorage(Plugin plugin, IStorageProvider storageProvider) {
PreCon.notNull(plugin);
PreCon.notNull(storageProvider);
IDataNode dataNode = _dataNode.getNode("storage");
synchronized (_pluginStorage) {
List<String> pluginNames = dataNode.getStringList(storageProvider.getInfo().getName(),
new ArrayList<String>(5));
assert pluginNames != null;
pluginNames.add(plugin.getName());
dataNode.set(storageProvider.getInfo().getName(), pluginNames);
}
dataNode.save();
}
@Nullable
@Override
public IStorageProvider getStorage(String name) {
PreCon.notNullOrEmpty(name);
return _storageProviders.get(name.toLowerCase());
}
@Override
public List<IStorageProvider> getStorageProviders() {
return new ArrayList<>(_storageProviders.values());
}
@Override
public <T extends Collection<IStorageProvider>> T getStorageProviders(T output) {
PreCon.notNull(output);
output.addAll(_storageProviders.values());
return output;
}
@Override
public IFastMathProvider getMath() {
// lazy load default provider to prevent it from being loaded
// if never used, specify default provider as the preferred
// provider to force load at startup
if (_math == null) {
_math = new NucleusFastMathProvider();
_math.registerTypes();
_math.enable();
}
return _math;
}
@Override
public Collection<String> getNames() {
return getNames(new ArrayList<String>(_providerInfo.size()));
}
@Override
public <T extends Collection<String>> T getNames(T output) {
PreCon.notNull(output);
for (ProviderInfo info : _providerInfo.values())
output.add(info.name);
return output;
}
@Override
public Collection<String> getNames(ProviderType type) {
PreCon.notNull(type);
return new HashSet<>(_providerNamesByApi.get(type));
}
@Override
public <T extends Collection<String>> T getNames(ProviderType providerType, T output) {
PreCon.notNull(providerType);
PreCon.notNull(output);
output.addAll(_providerNamesByApi.get(providerType));
return output;
}
/**
* Register a storage provider.
*
* @param storageProvider The storage provider to register
*/
public void registerStorageProvider(IStorageProvider storageProvider) {
PreCon.notNull(storageProvider);
IStorageProvider previous = _storageProviders.put(
storageProvider.getInfo().getSearchName(), storageProvider);
if (previous instanceof IDisposable && previous != storageProvider) {
((IDisposable) previous).dispose();
}
IDataNode dataNode = _dataNode.getNode("storage");
List<String> pluginNames = dataNode.getStringList(storageProvider.getInfo().getName(), null);
if (pluginNames != null) {
for (String pluginName : pluginNames) {
_pluginStorage.put(pluginName.toLowerCase(), storageProvider);
}
}
}
/**
* Set the loading flag.
*
* <p>Loads required default providers if not set when flag is set to false.</p>
*/
void setLoading(boolean isLoading) {
_isProvidersLoading = isLoading;
// load default providers that must be loaded immediately.
if (!isLoading) {
if (_regionSelect == null) {
_regionSelect = add(WorldEditSelectionProvider.isWorldEditInstalled()
? new WorldEditSelectionProvider()
: new NucleusSelectionProvider());
}
if (_playerLookup == null)
_playerLookup = add(new InternalPlayerLookupProvider(Nucleus.getPlugin()));
}
}
/*
* Remove provider from load list, returns true unless specified provider is the
* preferred provider, in which case the provider is not removed.
*/
private boolean remove(@Nullable IProvider provider, ProviderType providerType) {
if (provider == null)
return true;
String preferred = _dataNode.getString("preferred." + providerType.getName());
if (preferred != null && provider.getInfo().getName().equalsIgnoreCase(preferred))
return false;
_toEnable.remove(provider);
return true;
}
/*
* Add provider to load list.
*/
private <T extends IProvider> T add(IProvider provider) {
_toEnable.add(provider);
@SuppressWarnings("unchecked")
T cast = (T)provider;
return cast;
}
/*
* Add providers ProviderInfo and type->name lookup
*/
private void addName(IProvider provider, ProviderType type) {
_providerInfo.put(provider.getInfo().getSearchName(), new ProviderInfo(provider, type));
_providerNamesByApi.put(type, provider.getInfo().getName());
}
/*
* Add providers ProviderInfo and type->name lookup
*/
private void addName(String name, ProviderType type) {
_providerInfo.put(name.toLowerCase(), new ProviderInfo(name, type));
_providerNamesByApi.put(type, name);
}
private static class ProviderInfo {
String name;
String version;
ProviderType type;
ProviderInfo(IProvider provider, ProviderType type) {
this.name = provider.getInfo().getName();
this.version = provider.getInfo().getVersion();
this.type = type;
}
ProviderInfo(String name, ProviderType type) {
this.name = name;
this.version = Nucleus.getPlugin().getDescription().getVersion();
this.type = type;
}
}
}