/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package sander; import java.io.IOException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; /** * * @author Fernando */ public class UUIDResolver { private static final String USERNAME_DATA = "https://api.mojang.com/users/profiles/minecraft/%USERNAME%"; private static final String USERNAME_DATA_TIME = "https://api.mojang.com/users/profiles/minecraft/%USERNAME%?at=%TIMESTAMP%"; private static final String UUID_PREVIOUS_NAMES = "https://api.mojang.com/user/profiles/%UUID%/names"; private static final String UUID_SKIN_INFO = "https://sessionserver.mojang.com/session/minecraft/profile/%UUID%?unsigned=false"; private final Plugin plugin; public UUIDResolver(Plugin plugin) { this.plugin = plugin; } /** * The cache used to store player names to uuid's */ private final Map, List>>> playerNameCache = new HashMap<>(); public UUID getUUIDAsync(String username) throws InterruptedException, ExecutionException { return Bukkit.getScheduler().>callSyncMethod(plugin, new Callable>() { @Override public Future call() throws Exception { return getUUID(username); } }).get().get(); } /** * Gets the UUID from a username, This method needs to be called from the * main thread * * @param userName Thge username to lookup * @return Returns a Future object, do not call get() on it from the main * bukkit thread */ public Future getUUID(String userName) { return this.getUUID(userName, null); } /** * Gets the UUID from a username, This method needs to be called from the * main thread * * @param userName Thge username to lookup * @param callBack This will be called when the lookup is done * @return Returns a Future object, do not call get() on it from the main * bukkit thread */ public Future getUUID(String userName, Callback callBack) { return this.resolve(userName, playerNameCache, callBack, uuidConverter, "https://api.mojang.com/users/profiles/minecraft/%s", userName); } /** * The cache used to store player names and times to uuid's */ private final Map, Pair, List>>> playerNameTimeCache = new HashMap<>(); /** * Gets the UUID from a username, This method needs to be called from the * main thread * * @param userName Thge username to lookup * @param unixTime Time to lookup * @return Returns a Future object, do not call get() on it from the main * bukkit thread */ public Future getUUID(String userName, long unixTime) { return this.getUUID(userName, unixTime, null); } /** * Gets the UUID from a username, This method needs to be called from the * main thread * * @param userName Thge username to lookup * @param unixTime Time to lookup * @param callBack This will be called when the lookup is done * @return Returns a Future object, do not call get() on it from the main * bukkit thread */ public Future getUUID(String userName, long unixTime, Callback callBack) { return this.>resolve(new Pair<>(userName, unixTime), playerNameTimeCache, callBack, uuidConverter, "https://api.mojang.com/users/profiles/minecraft/%s?at=%d", userName, unixTime); } /** * The cache used to store player names to uuid's */ private final Map, List>>> playerDataCache = new HashMap<>(); public JSONObject getInternalDataAsync(UUID uuid) throws InterruptedException, ExecutionException { return Bukkit.getScheduler().>callSyncMethod(plugin, new Callable>() { @Override public Future call() throws Exception { return getInternalData(uuid); } }).get().get(); } public Future getInternalData(UUID userName) { return this.getInternalData(userName,null); } /** * This method needs to be called from the * main thread * * @param userName The UUID to lookup * @param callBack This will be called when the lookup is done * @return Returns a Future object, do not call get() on it from the main * bukkit thread */ public Future getInternalData(UUID userName, Callback callBack) { return this.resolve(userName, playerDataCache, callBack, new ResponseResolver() { @Override public JSONObject resolveResult(JSONObject obj) { return obj; } }, "https://api.mojang.com/users/profiles/minecraft/%s", userName.toString().replaceAll("-", "")); } /** * Read a json object from a url * * @param url * @return * @throws ParseException * @throws IOException */ private JSONObject readJSONFromURL(URL url) throws ParseException, IOException { URLConnection uc = url.openConnection(); uc.setUseCaches(true); uc.setDefaultUseCaches(true); uc.addRequestProperty("User-Agent", "Java Bigteddy98 UUID Library"); try (Scanner scanner = new Scanner(uc.getInputStream())) { scanner.useDelimiter("\\A"); return (JSONObject) new JSONParser().parse(scanner.next()); } } /** * Private class used to convert the json response to the correct object */ private interface ResponseResolver { public R resolveResult(JSONObject obj); } /** * Private class used to convert the json response to a uuid */ private static final ResponseResolver uuidConverter = new ResponseResolver() { @Override public UUID resolveResult(JSONObject obj) { return UUID.fromString(obj.get("id").toString().replaceAll( "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); } }; /** * Private method used to resolve the queries * * @param The result of the operation * @param The unique input of the operation, this will be used to lookup * the cache result * @param cacheIndex This is the unique value of the result in the cache * @param cache The Map<...> object used to hold the cached values * @param callback This object will be called when the request is done * @param request Its the job of the RequestResolver to convert * the JSONObject back to the object the code needs * @param baseUrl The main url used for this operation, this will be passed * to String.format * @param formats The arguments for String.format * @return This method returns a Future to see what the result of the * request is */ private Future resolve( I cacheIndex, Map, List>>> cache, Callback callback, ResponseResolver request, String baseUrl, Object... formats) { if (!Bukkit.isPrimaryThread()) { throw new IllegalStateException( "You need to call this method from the main thread " + "because of the request limits Mojang has configured"); } if (!cache.containsKey(cacheIndex)) { final List> callBackList = new LinkedList<>(); FutureTask task = new FutureTask(new Callable() { @Override public R call() throws Exception { assert !Bukkit.isPrimaryThread(); return request.resolveResult(UUIDResolver.this.readJSONFromURL( new URL(String.format(baseUrl, (Object[]) formats)) )); } }) { @Override protected void done() { FutureTask th = this; new BukkitRunnable() { @Override public void run() { assert Bukkit.isPrimaryThread(); for (Callback id : callBackList) { id.onCallBack(th); } callBackList.clear(); } }.runTask(plugin); } }; cache.put(cacheIndex, new Pair<>(task, callBackList)); Bukkit.getScheduler().runTaskAsynchronously(plugin, task); } assert cache.containsKey(cacheIndex); Pair, List>> result = cache.get(cacheIndex); if (result.first.isDone()) { if (!result.second.isEmpty()) { for (Callback c : result.second) { c.onCallBack(result.first); } result.second = Collections.emptyList(); } if (callback != null) { callback.onCallBack(result.first); } } else { if (callback != null) { result.second.add(callback); } } return result.first; } /** * This class contains a pair of other objects *

* Taken from:
* StackOverflow * * @param first type * @param second type */ private static class Pair { private A first; private B second; public Pair(A first, B second) { super(); this.first = first; this.second = second; } @Override public int hashCode() { int hashFirst = first != null ? first.hashCode() : 0; int hashSecond = second != null ? second.hashCode() : 0; return (hashFirst + hashSecond) * hashSecond + hashFirst; } @Override public boolean equals(Object other) { if (other instanceof Pair) { Pair otherPair = (Pair) other; return ((this.first == otherPair.first || (this.first != null && otherPair.first != null && this.first.equals(otherPair.first))) && (this.second == otherPair.second || (this.second != null && otherPair.second != null && this.second.equals(otherPair.second)))); } return false; } @Override public String toString() { return "(" + first + ", " + second + ")"; } public A getFirst() { return first; } public void setFirst(A first) { this.first = first; } public B getSecond() { return second; } public void setSecond(B second) { this.second = second; } } /** * Class used to pass the results back to the parent stack * * @param */ public interface Callback { /** * This method is called when the lookup is done * * @param result The result of the operation */ public void onCallBack(FutureTask result); } }