Add paralellism to the Fetcher framework. Seems to reduce time spent from O(N) to O(1).

This commit is contained in:
Olof Larsson 2014-04-10 17:07:45 +02:00
parent 666e4d0dff
commit 2b6cce2607
9 changed files with 740 additions and 341 deletions

View File

@ -177,12 +177,22 @@ public class MCore extends MPlugin
VaultFeatures.get()
);
//test();
// Delete Files (at once and additionally after all plugins loaded)
TaskDeleteFiles.get().run();
Bukkit.getScheduler().scheduleSyncDelayedTask(this, TaskDeleteFiles.get());
//test();
// Schedule fetch all
Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable()
{
@Override
public void run()
{
PlayerUtil.fetchAllIds();
}
});
this.postEnable();
}

View File

@ -0,0 +1,94 @@
package com.massivecraft.mcore.fetcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import com.massivecraft.mcore.MCoreMPlayer;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class FetcherPlayerIdCached implements Callable<Map<String, UUID>>
{
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private final Collection<String> playerNames;
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FetcherPlayerIdCached(Collection<String> playerNames)
{
this.playerNames = playerNames;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public Map<String, UUID> call() throws Exception
{
return fetch(this.playerNames);
}
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public static Map<String, UUID> fetch(Collection<String> playerNames) throws Exception
{
Map<String, UUID> ret = new TreeMap<String, UUID>(String.CASE_INSENSITIVE_ORDER);
List<String> playerNamesCopy = new ArrayList<String>(playerNames);
// Use Cache
Iterator<String> iter = playerNamesCopy.iterator();
while (iter.hasNext())
{
String playerName = iter.next();
MCoreMPlayer mplayer = MCoreMPlayer.get(playerName);
if (mplayer == null) continue;
ret.put(mplayer.getName(), UUID.fromString(mplayer.getId()));
iter.remove();
}
// Use Mojang API for the rest
if (playerNamesCopy.size() > 0)
{
try
{
Map<String, UUID> mojangApiResult = FetcherPlayerIdMojang.fetch(playerNamesCopy);
// Add to the cache
for (Entry<String, UUID> entry : mojangApiResult.entrySet())
{
String name = entry.getKey();
UUID id = entry.getValue();
MCoreMPlayer mplayer = MCoreMPlayer.get(id, true);
mplayer.setName(name);
}
// Add to the return value
ret.putAll(mojangApiResult);
}
catch (Exception e)
{
e.printStackTrace();
}
}
// Return
return ret;
}
}

View File

@ -0,0 +1,106 @@
package com.massivecraft.mcore.fetcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class FetcherPlayerIdMojang implements Callable<Map<String, UUID>>
{
// -------------------------------------------- //
// CONSTANTS
// -------------------------------------------- //
public static final int BATCH_SIZE = FetcherPlayerIdMojangSingle.MAX_PAGE_SIZE;
public static final ExecutorService ES = Executors.newCachedThreadPool();
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private final Collection<String> playerNames;
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FetcherPlayerIdMojang(Collection<String> playerNames)
{
this.playerNames = playerNames;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public Map<String, UUID> call() throws Exception
{
return fetch(this.playerNames);
}
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public static Map<String, UUID> fetch(Collection<String> playerNames) throws Exception
{
// Create batches
List<List<String>> batches = new ArrayList<List<String>>();
playerNames = new ArrayList<String>(playerNames);
while (playerNames.size() > 0)
{
List<String> batch = take(playerNames, BATCH_SIZE);
batches.add(batch);
}
// Create Tasks
final List<Callable<Map<String, UUID>>> tasks = new ArrayList<Callable<Map<String, UUID>>>();
for (List<String> batch : batches)
{
tasks.add(new FetcherPlayerIdMojangSingle(batch));
}
// Invoke All Tasks
List<Future<Map<String, UUID>>> futures = ES.invokeAll(tasks);
// Merge Return Value
Map<String, UUID> ret = new TreeMap<String, UUID> (String.CASE_INSENSITIVE_ORDER);
for (Future<Map<String, UUID>> future : futures)
{
ret.putAll(future.get());
}
return ret;
}
public static <T> List<T> take(Collection<T> coll, int count)
{
List<T> ret = new ArrayList<T>();
Iterator<T> iter = coll.iterator();
int i = 0;
while (iter.hasNext() && i < count)
{
i++;
ret.add(iter.next());
iter.remove();
}
return ret;
}
}

View File

@ -0,0 +1,143 @@
package com.massivecraft.mcore.fetcher;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.Callable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.JSONParser;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class FetcherPlayerIdMojangSingle implements Callable<Map<String, UUID>>
{
// -------------------------------------------- //
// CONSTANTS
// -------------------------------------------- //
public final static String URL_BASE = "https://api.mojang.com/profiles/page/";
public final static int MAX_PAGES = 100;
// The maximum amount of profiles returned per page.
// Mojang might change this value.
// Thus we can not fully depend on it.
public final static int MAX_PAGE_SIZE = 50;
public final static String KEY_PROFILES = "profiles";
public final static String KEY_SIZE = "size";
public final static String KEY_ID = "id";
public final static String KEY_NAME = "name";
public final static String KEY_AGENT = "agent";
public final static String VALUGE_AGENT = "minecraft";
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private final Collection<String> playerNames;
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FetcherPlayerIdMojangSingle(Collection<String> playerNames)
{
this.playerNames = playerNames;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public Map<String, UUID> call() throws Exception
{
return fetch(this.playerNames);
}
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public static Map<String, UUID> fetch(Collection<String> playerNames) throws Exception
{
Map<String, UUID> ret = new TreeMap<String, UUID>(String.CASE_INSENSITIVE_ORDER);
JSONParser jsonParser = new JSONParser();
String body = createBody(playerNames);
for (int i = 1; i < MAX_PAGES; i++)
{
// If the return object has as many entries as player names requested we must have gotten all the info.
// This will often help us avoid the extra useless last call of a page with 0 entries.
if (ret.size() == playerNames.size()) break;
HttpURLConnection connection = createConnection(i);
writeBody(connection, body);
JSONObject jsonObject = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
JSONArray profiles = (JSONArray) jsonObject.get(KEY_PROFILES);
int size = ((Number) jsonObject.get(KEY_SIZE)).intValue();
// If the page is empty we are done
if (size == 0) break;
for (Object profile : profiles)
{
JSONObject jsonProfile = (JSONObject) profile;
String id = (String) jsonProfile.get(KEY_ID);
String name = (String) jsonProfile.get(KEY_NAME);
UUID uuid = UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32));
ret.put(name, uuid);
}
}
return ret;
}
private static HttpURLConnection createConnection(int page) throws Exception
{
URL url = new URL(URL_BASE + page);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
@SuppressWarnings("unchecked")
private static String createBody(Collection<String> playerNames)
{
List<JSONObject> lookups = new ArrayList<JSONObject>();
for (String playerName : playerNames)
{
JSONObject obj = new JSONObject();
obj.put(KEY_NAME, playerName);
obj.put(KEY_AGENT, VALUGE_AGENT);
lookups.add(obj);
}
return JSONValue.toJSONString(lookups);
}
private static void writeBody(HttpURLConnection connection, String body) throws Exception
{
DataOutputStream writer = new DataOutputStream(connection.getOutputStream());
writer.write(body.getBytes());
writer.flush();
writer.close();
}
}

View File

@ -0,0 +1,93 @@
package com.massivecraft.mcore.fetcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import com.massivecraft.mcore.MCoreMPlayer;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class FetcherPlayerNameCached implements Callable<Map<UUID, String>>
{
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private final Collection<UUID> playerIds;
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FetcherPlayerNameCached(Collection<UUID> playerIds)
{
this.playerIds = playerIds;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public Map<UUID, String> call() throws Exception
{
return fetch(this.playerIds);
}
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public static Map<UUID, String> fetch(Collection<UUID> playerIds) throws Exception
{
Map<UUID, String> ret = new HashMap<UUID, String>();
List<UUID> playerIdsCopy = new ArrayList<UUID>(playerIds);
// Use Cache
Iterator<UUID> iter = playerIdsCopy.iterator();
while (iter.hasNext())
{
UUID playerId = iter.next();
MCoreMPlayer mplayer = MCoreMPlayer.get(playerId);
if (mplayer == null) continue;
ret.put(playerId, mplayer.getName());
iter.remove();
}
// Use Mojang API for the rest
if (playerIdsCopy.size() > 0)
{
try
{
Map<UUID, String> mojangApiResult = FetcherPlayerNameMojang.fetch(playerIdsCopy);
// Add to the cache
for (Entry<UUID, String> entry : mojangApiResult.entrySet())
{
UUID id = entry.getKey();
String name = entry.getValue();
MCoreMPlayer mplayer = MCoreMPlayer.get(id, true);
mplayer.setName(name);
}
// Add to the return value
ret.putAll(mojangApiResult);
}
catch (Exception e)
{
e.printStackTrace();
}
}
// Return
return ret;
}
}

View File

@ -0,0 +1,81 @@
package com.massivecraft.mcore.fetcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class FetcherPlayerNameMojang implements Callable<Map<UUID, String>>
{
// -------------------------------------------- //
// CONSTANTS
// -------------------------------------------- //
public static final ExecutorService ES = Executors.newCachedThreadPool();
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private final Collection<UUID> playerIds;
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FetcherPlayerNameMojang(Collection<UUID> playerIds)
{
this.playerIds = playerIds;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public Map<UUID, String> call() throws Exception
{
return fetch(this.playerIds);
}
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public static Map<UUID, String> fetch(Collection<UUID> playerIds) throws Exception
{
// Create Tasks
final List<Callable<Entry<UUID, String>>> tasks = new ArrayList<Callable<Entry<UUID, String>>>();
for (UUID playerId : playerIds)
{
tasks.add(new FetcherPlayerNameMojangSingle(playerId));
}
// Invoke All Tasks
List<Future<Entry<UUID, String>>> futures = ES.invokeAll(tasks);
// Merge Return Value
Map<UUID, String> ret = new HashMap<UUID, String>();
for (Future<Entry<UUID, String>> future : futures)
{
Entry<UUID, String> entry = future.get();
if (entry == null) continue;
ret.put(entry.getKey(), entry.getValue());
}
return ret;
}
}

View File

@ -0,0 +1,90 @@
package com.massivecraft.mcore.fetcher;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map.Entry;
import java.util.AbstractMap.SimpleEntry;
import java.util.UUID;
import java.util.concurrent.Callable;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class FetcherPlayerNameMojangSingle implements Callable<Entry<UUID, String>>
{
// -------------------------------------------- //
// CONSTANTS
// -------------------------------------------- //
public final static String URL_BASE = "https://sessionserver.mojang.com/session/minecraft/profile/";
public final static String KEY_NAME = "name";
public final static String KEY_CAUSE = "cause";
public final static String KEY_ERROR_MESSAGE = "errorMessage";
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private final UUID playerId;
public UUID getPlayerId() { return this.playerId; }
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public FetcherPlayerNameMojangSingle(UUID playerId)
{
this.playerId = playerId;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public Entry<UUID, String> call() throws Exception
{
String playerName = fetch(this.playerId);
if (playerName == null) return null;
return new SimpleEntry<UUID, String>(this.playerId, playerName);
}
// -------------------------------------------- //
// STATIC
// -------------------------------------------- //
public static String fetch(UUID playerId) throws Exception
{
JSONParser jsonParser = new JSONParser();
HttpURLConnection connection = createConnection(playerId);
JSONObject response = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
String name = (String) response.get(KEY_NAME);
if (name == null) return null;
String cause = (String) response.get(KEY_CAUSE);
if (cause != null && cause.length() > 0)
{
String errorMessage = (String) response.get(KEY_ERROR_MESSAGE);
throw new IllegalStateException(errorMessage);
}
return name;
}
private static HttpURLConnection createConnection(UUID playerId) throws Exception
{
URL url = new URL(URL_BASE + playerId.toString().replace("-", ""));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
}

View File

@ -1,124 +0,0 @@
package com.massivecraft.mcore.util;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.JSONParser;
/**
* Many thanks to evilmidget38!
* This utility class is based on his work.
* http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class MojangApiUtil
{
// -------------------------------------------- //
// NAME --> ID
// -------------------------------------------- //
// The player names you supply does not have to use correct capitalization.
// In the map returned however, the names will have correction capitalization.
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames) throws Exception
{
Map<String, UUID> ret = new HashMap<String, UUID>();
JSONParser jsonParser = new JSONParser();
String body = createBody(playerNames);
for (int i = 1; i < 100; i++)
{
HttpURLConnection connection = createConnection(i);
writeBody(connection, body);
JSONObject jsonObject = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
JSONArray profiles = (JSONArray) jsonObject.get("profiles");
Number count = (Number) jsonObject.get("size");
if (count.intValue() == 0)
{
break;
}
for (Object profile : profiles)
{
JSONObject jsonProfile = (JSONObject) profile;
String id = (String) jsonProfile.get("id");
String name = (String) jsonProfile.get("name");
UUID uuid = UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32));
ret.put(name, uuid);
}
}
return ret;
}
private static void writeBody(HttpURLConnection connection, String body) throws Exception
{
DataOutputStream writer = new DataOutputStream(connection.getOutputStream());
writer.write(body.getBytes());
writer.flush();
writer.close();
}
private static HttpURLConnection createConnection(int page) throws Exception
{
URL url = new URL("https://api.mojang.com/profiles/page/" + page);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
@SuppressWarnings("unchecked")
private static String createBody(Collection<String> playerNames)
{
List<JSONObject> lookups = new ArrayList<JSONObject>();
for (String playerName : playerNames)
{
JSONObject obj = new JSONObject();
obj.put("name", playerName);
obj.put("agent", "minecraft");
lookups.add(obj);
}
return JSONValue.toJSONString(lookups);
}
// -------------------------------------------- //
// ID --> NAME
// -------------------------------------------- //
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds) throws Exception
{
Map<UUID, String> ret = new HashMap<UUID, String>();
JSONParser jsonParser = new JSONParser();
for (UUID playerId: playerIds)
{
HttpURLConnection connection = (HttpURLConnection) new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + playerId.toString().replace("-", "")).openConnection();
JSONObject response = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
String name = (String) response.get("name");
if (name == null)
{
continue;
}
String cause = (String) response.get("cause");
if (cause != null && cause.length() > 0)
{
String errorMessage = (String) response.get("errorMessage");
throw new IllegalStateException(errorMessage);
}
ret.put(playerId, name);
}
return ret;
}
}

View File

@ -7,9 +7,9 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
@ -29,6 +29,11 @@ import org.bukkit.event.player.PlayerQuitEvent;
import com.massivecraft.mcore.MCore;
import com.massivecraft.mcore.MCoreMPlayer;
import com.massivecraft.mcore.fetcher.FetcherPlayerIdCached;
import com.massivecraft.mcore.fetcher.FetcherPlayerNameCached;
import com.massivecraft.mcore.mixin.Mixin;
import com.massivecraft.mcore.store.Coll;
import com.massivecraft.mcore.store.SenderColl;
public class PlayerUtil implements Listener
{
@ -152,42 +157,7 @@ public class PlayerUtil implements Listener
// PLAYER ID <---> PLAYER NAME
// -------------------------------------------- //
// getPlayerName
public static String getPlayerName(final UUID playerId, final boolean usingCache, final boolean usingMojangApi)
{
List<UUID> playerIds = Collections.singletonList(playerId);
Map<UUID, String> map = getPlayerNames(playerIds, usingCache, usingMojangApi);
return map.get(playerId);
}
public static String getPlayerName(final UUID playerId, final boolean usingCache)
{
return getPlayerName(playerId, usingCache, true);
}
public static String getPlayerName(final UUID playerId)
{
return getPlayerName(playerId, true);
}
// getPlayerId
public static UUID getPlayerId(final String playerName, final boolean usingCache, final boolean usingMojangApi)
{
List<String> playerNames = Collections.singletonList(playerName);
Map<String, UUID> map = getPlayerIds(playerNames, usingCache, usingMojangApi);
return map.get(playerName);
}
public static UUID getPlayerId(final String playerName, final boolean usingCache)
{
return getPlayerId(playerName, usingCache, true);
}
public static UUID getPlayerId(final String playerName)
{
return getPlayerId(playerName, true);
}
// Update Cache on Login
@EventHandler(priority = EventPriority.LOWEST)
public void playerIdPlayerName(PlayerLoginEvent event)
{
@ -197,191 +167,127 @@ public class PlayerUtil implements Listener
mplayer.setName(playerName);
}
// Core Methods
// I suggest using ...
// final Map<String, UUID> ret = new TreeMap<String, UUID>(String.CASE_INSENSITIVE_ORDER);
// ... since you achieve case insensitivity that way.
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames, final boolean usingCache, final boolean usingMojangApi, final boolean synchronous, final Runnable onComplete, Map<String, UUID> ret)
{
// Finalize Args
// To run the Async task we need final versions of all arguments.
// We shallow copy the array for the sake of concurrency and that we will want to remove those we could handle from cache in order to avoid contacting the mojang api in vain.
// We need a return value map. Create one if null. Here we do however not shallow copy. We aim to edit the supplied map instance so that it can be used inside the onComplete Runnable.
final List<String> playerNamesFinal = new ArrayList<String>(playerNames);
final Map<String, UUID> retFinal = (ret != null ? ret : new TreeMap<String, UUID>(String.CASE_INSENSITIVE_ORDER));
// Handle Async
// Just run sync from another thread.
if (!synchronous)
{
Bukkit.getScheduler().runTaskAsynchronously(MCore.get(), new Runnable()
{
@Override
public void run()
{
PlayerUtil.getPlayerIds(playerNamesFinal, usingCache, usingMojangApi, true, onComplete, retFinal);
}
});
return retFinal;
}
// Handle Cache
if (usingCache)
{
Iterator<String> iter = playerNamesFinal.iterator();
while (iter.hasNext())
{
String playerName = iter.next();
MCoreMPlayer mplayer = MCoreMPlayer.get(playerName);
if (mplayer == null) continue;
retFinal.put(mplayer.getName(), UUID.fromString(mplayer.getId()));
iter.remove();
}
}
// Handle Mojang Api
if (usingMojangApi && playerNamesFinal.size() > 0)
{
try
{
Map<String, UUID> mojangApiResult = MojangApiUtil.getPlayerIds(playerNamesFinal);
// Add to the cache
for (Entry<String, UUID> entry : mojangApiResult.entrySet())
{
String name = entry.getKey();
UUID id = entry.getValue();
MCoreMPlayer mplayer = MCoreMPlayer.get(id, true);
mplayer.setName(name);
}
// Add to the return value
retFinal.putAll(mojangApiResult);
}
catch (Exception e)
{
e.printStackTrace();
}
}
// Run the onComplete task.
if (onComplete != null)
{
onComplete.run();
}
// Return
return retFinal;
}
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames, final boolean usingCache, final boolean usingMojangApi, final boolean synchronous, final Runnable onComplete)
{
return getPlayerIds(playerNames, usingCache, usingMojangApi, synchronous, onComplete, null);
}
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames, final boolean usingCache, final boolean usingMojangApi, final boolean synchronous)
{
return getPlayerIds(playerNames, usingCache, usingMojangApi, synchronous, null);
}
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames, final boolean usingCache, final boolean usingMojangApi)
{
return getPlayerIds(playerNames, usingCache, usingMojangApi, true);
}
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames, final boolean usingCache)
{
return getPlayerIds(playerNames, usingCache, true);
}
public static Map<String, UUID> getPlayerIds(Collection<String> playerNames)
{
return getPlayerIds(playerNames, true);
try
{
return FetcherPlayerIdCached.fetch(playerNames);
}
catch (Exception e)
{
e.printStackTrace();
return new TreeMap<String, UUID>(String.CASE_INSENSITIVE_ORDER);
}
}
public static UUID getPlayerId(String playerName)
{
List<String> playerNames = Collections.singletonList(playerName);
Map<String, UUID> map = getPlayerIds(playerNames);
return map.get(playerName);
}
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds, final boolean usingCache, final boolean usingMojangApi, final boolean synchronous, final Runnable onComplete, Map<UUID, String> ret)
{
// Finalize Args
// To run the Async task we need final versions of all arguments.
// We shallow copy the array for the sake of concurrency and that we will want to remove those we could handle from cache in order to avoid contacting the mojang api in vain.
// We need a return value map. Create one if null. Here we do however not shallow copy. We aim to edit the supplied map instance so that it can be used inside the onComplete Runnable.
final List<UUID> playerIdsFinal = new ArrayList<UUID>(playerIds);
final Map<UUID, String> retFinal = (ret != null ? ret : new HashMap<UUID, String>());
// Handle Async
// Just run sync from another thread.
if (!synchronous)
{
Bukkit.getScheduler().runTaskAsynchronously(MCore.get(), new Runnable()
{
@Override
public void run()
{
PlayerUtil.getPlayerNames(playerIdsFinal, usingCache, usingMojangApi, true, onComplete, retFinal);
}
});
return retFinal;
}
// Handle Cache
if (usingCache)
{
Iterator<UUID> iter = playerIdsFinal.iterator();
while (iter.hasNext())
{
UUID playerId = iter.next();
MCoreMPlayer mplayer = MCoreMPlayer.get(playerId);
if (mplayer == null) continue;
retFinal.put(playerId, mplayer.getName());
iter.remove();
}
}
// Handle Mojang Api
if (usingMojangApi && playerIdsFinal.size() > 0)
{
try
{
Map<UUID, String> mojangApiResult = MojangApiUtil.getPlayerNames(playerIdsFinal);
// Add to the cache
for (Entry<UUID, String> entry : mojangApiResult.entrySet())
{
UUID id = entry.getKey();
String name = entry.getValue();
MCoreMPlayer mplayer = MCoreMPlayer.get(id, true);
mplayer.setName(name);
}
// Add to the return value
retFinal.putAll(mojangApiResult);
}
catch (Exception e)
{
e.printStackTrace();
}
}
// Run the onComplete task.
if (onComplete != null)
{
onComplete.run();
}
// Return
return retFinal;
}
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds, final boolean usingCache, final boolean usingMojangApi, final boolean synchronous, final Runnable onComplete)
{
return getPlayerNames(playerIds, usingCache, usingMojangApi, synchronous, onComplete, null);
}
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds, final boolean usingCache, final boolean usingMojangApi, final boolean synchronous)
{
return getPlayerNames(playerIds, usingCache, usingMojangApi, synchronous, null);
}
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds, final boolean usingCache, final boolean usingMojangApi)
{
return getPlayerNames(playerIds, usingCache, usingMojangApi, true);
}
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds, final boolean usingCache)
{
return getPlayerNames(playerIds, usingCache, true);
}
public static Map<UUID, String> getPlayerNames(Collection<UUID> playerIds)
{
return getPlayerNames(playerIds, true);
try
{
return FetcherPlayerNameCached.fetch(playerIds);
}
catch (Exception e)
{
e.printStackTrace();
return new HashMap<UUID, String>();
}
}
public static String getPlayerName(UUID playerId)
{
List<UUID> playerIds = Collections.singletonList(playerId);
Map<UUID, String> map = getPlayerNames(playerIds);
return map.get(playerId);
}
// -------------------------------------------- //
// PLAYER ID <---> PLAYER NAME: FETCH ALL
// -------------------------------------------- //
public static void fetchAllIds()
{
// --- Starting Information ---
MCore.get().log(Txt.parse("<a>============================================"));
MCore.get().log(Txt.parse("<i>We are preparing for Mojangs switch to UUIDs."));
MCore.get().log(Txt.parse("<i>Learn more at: <aqua>https://forums.bukkit.org/threads/psa-the-switch-to-uuids-potential-plugin-server-breakage.250915/"));
MCore.get().log(Txt.parse("<i>Now fetching and caching UUID for all player names on this server!"));
MCore.get().log(Txt.parse("<i>The mstore collection \"<h>mcore_mplayer<i>\" will contain the cached information."));
// --- Find Player Names ---
// Here we build a set containing all player names we know of!
Set<String> playerNames = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
// All from mixin
playerNames.addAll(Mixin.getAllPlayerIds());
// All from sender colls
for (Coll<?> coll : Coll.getInstances())
{
if (!(coll instanceof SenderColl<?>)) continue;
playerNames.addAll(coll.getIds());
}
// Only valid player names
Iterator<String> iter = playerNames.iterator();
while (iter.hasNext())
{
String playerName =iter.next();
if (MUtil.isValidPlayerName(playerName)) continue;
iter.remove();
}
// Report: Player Names Found
MCore.get().log(Txt.parse("<k>Player Names Found: <v>%d", playerNames.size()));
// --- Remove Cached ---
// Here we remove what we already have cached.
iter = playerNames.iterator();
int cached = 0;
while (iter.hasNext())
{
String playerName = iter.next();
MCoreMPlayer mplayer = MCoreMPlayer.get(playerName);
if (mplayer == null) continue;
if (mplayer.getName() == null) continue;
cached++;
iter.remove();
}
MCore.get().log(Txt.parse("<k>Player Names Cached: <v>%d", cached));
MCore.get().log(Txt.parse("<k>Player Names Remaining: <v>%d", playerNames.size()));
// --- Fetch ---
// Here we fetch the remaining player names.
// We fetch them through the cached fetcher.
// This way we will use the mojang fetcher but also cache the result for the future.
MCore.get().log(Txt.parse("<i>Now fetching the remaining players from Mojang API ..."));
getPlayerIds(playerNames);
MCore.get().log(Txt.parse("<g> ... done!"));
MCore.get().log(Txt.parse("<i>(database saving will now commence which might lock the server for a while)"));
MCore.get().log(Txt.parse("<a>============================================"));
}
public static <T> List<T> take(Collection<T> coll, int count)
{
List<T> ret = new ArrayList<T>();
Iterator<T> iter = coll.iterator();
int i = 0;
while (iter.hasNext() && i < count)
{
i++;
ret.add(iter.next());
iter.remove();
}
return ret;
}
}