ferrybig

UUIDResolver.java - A bukkit coding project

Jul 28th, 2014
458
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 12.81 KB | None | 0 0
  1. /*
  2.  * To change this license header, choose License Headers in Project Properties.
  3.  * To change this template file, choose Tools | Templates
  4.  * and open the template in the editor.
  5.  */
  6. package sander;
  7.  
  8. import java.io.IOException;
  9. import java.net.URL;
  10. import java.net.URLConnection;
  11. import java.util.ArrayList;
  12. import java.util.Collections;
  13. import java.util.HashMap;
  14. import java.util.LinkedList;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.Scanner;
  18. import java.util.UUID;
  19. import java.util.concurrent.Callable;
  20. import java.util.concurrent.ExecutionException;
  21. import java.util.concurrent.Future;
  22. import java.util.concurrent.FutureTask;
  23. import org.bukkit.Bukkit;
  24. import org.bukkit.plugin.Plugin;
  25. import org.bukkit.scheduler.BukkitRunnable;
  26. import org.json.simple.JSONObject;
  27. import org.json.simple.parser.JSONParser;
  28. import org.json.simple.parser.ParseException;
  29.  
  30. /**
  31.  *
  32.  * @author Fernando
  33.  */
  34. public class UUIDResolver {
  35.  
  36.     private static final String USERNAME_DATA = "https://api.mojang.com/users/profiles/minecraft/%USERNAME%";
  37.     private static final String USERNAME_DATA_TIME = "https://api.mojang.com/users/profiles/minecraft/%USERNAME%?at=%TIMESTAMP%";
  38.     private static final String UUID_PREVIOUS_NAMES = "https://api.mojang.com/user/profiles/%UUID%/names";
  39.     private static final String UUID_SKIN_INFO = "https://sessionserver.mojang.com/session/minecraft/profile/%UUID%?unsigned=false";
  40.  
  41.     private final Plugin plugin;
  42.  
  43.     public UUIDResolver(Plugin plugin) {
  44.         this.plugin = plugin;
  45.     }
  46.     /**
  47.      * The cache used to store player names to uuid's
  48.      */
  49.     private final Map<String, Pair<FutureTask<UUID>, List<Callback<UUID>>>> playerNameCache = new HashMap<>();
  50.  
  51.     public UUID getUUIDAsync(String username) throws InterruptedException, ExecutionException
  52.     {
  53.         return Bukkit.getScheduler().<Future<UUID>>callSyncMethod(plugin, new Callable<Future<UUID>>() {
  54.  
  55.             @Override
  56.             public Future<UUID> call() throws Exception {
  57.                  return getUUID(username);
  58.             }
  59.         }).get().get();
  60.     }
  61.     /**
  62.      * Gets the UUID from a username, This method needs to be called from the
  63.      * main thread
  64.      *
  65.      * @param userName Thge username to lookup
  66.      * @return Returns a Future object, do not call get() on it from the main
  67.      * bukkit thread
  68.      */
  69.     public Future<UUID> getUUID(String userName) {
  70.         return this.getUUID(userName, null);
  71.     }
  72.  
  73.     /**
  74.      * Gets the UUID from a username, This method needs to be called from the
  75.      * main thread
  76.      *
  77.      * @param userName Thge username to lookup
  78.      * @param callBack This will be called when the lookup is done
  79.      * @return Returns a Future object, do not call get() on it from the main
  80.      * bukkit thread
  81.      */
  82.     public Future<UUID> getUUID(String userName, Callback<UUID> callBack) {
  83.         return this.<UUID, String>resolve(userName, playerNameCache, callBack,
  84.                 uuidConverter,
  85.                 "https://api.mojang.com/users/profiles/minecraft/%s", userName);
  86.     }
  87.     /**
  88.      * The cache used to store player names and times to uuid's
  89.      */
  90.     private final Map<Pair<String, Long>, Pair<FutureTask<UUID>, List<Callback<UUID>>>> playerNameTimeCache = new HashMap<>();
  91.  
  92.     /**
  93.      * Gets the UUID from a username, This method needs to be called from the
  94.      * main thread
  95.      *
  96.      * @param userName Thge username to lookup
  97.      * @param unixTime Time to lookup
  98.      * @return Returns a Future object, do not call get() on it from the main
  99.      * bukkit thread
  100.      */
  101.     public Future<UUID> getUUID(String userName, long unixTime) {
  102.         return this.getUUID(userName, unixTime, null);
  103.     }
  104.  
  105.     /**
  106.      * Gets the UUID from a username, This method needs to be called from the
  107.      * main thread
  108.      *
  109.      * @param userName Thge username to lookup
  110.      * @param unixTime Time to lookup
  111.      * @param callBack This will be called when the lookup is done
  112.      * @return Returns a Future object, do not call get() on it from the main
  113.      * bukkit thread
  114.      */
  115.     public Future<UUID> getUUID(String userName, long unixTime, Callback<UUID> callBack) {
  116.         return this.<UUID, Pair<String, Long>>resolve(new Pair<>(userName, unixTime), playerNameTimeCache, callBack,
  117.                 uuidConverter,
  118.                 "https://api.mojang.com/users/profiles/minecraft/%s?at=%d",
  119.                 userName, unixTime);
  120.     }
  121.     /**
  122.      * The cache used to store player names to uuid's
  123.      */
  124.     private final Map<UUID, Pair<FutureTask<JSONObject>, List<Callback<JSONObject>>>> playerDataCache = new HashMap<>();
  125.  
  126.     public JSONObject getInternalDataAsync(UUID uuid) throws InterruptedException, ExecutionException
  127.     {
  128.         return Bukkit.getScheduler().<Future<JSONObject>>callSyncMethod(plugin, new Callable<Future<JSONObject>>() {
  129.  
  130.             @Override
  131.             public Future<JSONObject> call() throws Exception {
  132.                  return getInternalData(uuid);
  133.             }
  134.         }).get().get();
  135.     }
  136.     public Future<JSONObject> getInternalData(UUID userName)
  137.     {
  138.         return this.getInternalData(userName,null);
  139.     }
  140.     /**
  141.      * This method needs to be called from the
  142.      * main thread
  143.      *
  144.      * @param userName The UUID to lookup
  145.      * @param callBack This will be called when the lookup is done
  146.      * @return Returns a Future object, do not call get() on it from the main
  147.      * bukkit thread
  148.      */
  149.     public Future<JSONObject> getInternalData(UUID userName, Callback<JSONObject> callBack) {
  150.         return this.<JSONObject, UUID>resolve(userName, playerDataCache, callBack,
  151.                 new ResponseResolver<JSONObject>() {
  152.  
  153.                     @Override
  154.                     public JSONObject resolveResult(JSONObject obj) {
  155.                         return obj;
  156.                     }
  157.                 },
  158.                 "https://api.mojang.com/users/profiles/minecraft/%s", userName.toString().replaceAll("-", ""));
  159.     }
  160.  
  161.     /**
  162.      * Read a json object from a url
  163.      *
  164.      * @param url
  165.      * @return
  166.      * @throws ParseException
  167.      * @throws IOException
  168.      */
  169.     private JSONObject readJSONFromURL(URL url)
  170.             throws ParseException, IOException {
  171.         URLConnection uc = url.openConnection();
  172.         uc.setUseCaches(true);
  173.         uc.setDefaultUseCaches(true);
  174.         uc.addRequestProperty("User-Agent", "Java Bigteddy98 UUID Library");
  175.  
  176.         try (Scanner scanner = new Scanner(uc.getInputStream())) {
  177.             scanner.useDelimiter("\\A");
  178.             return (JSONObject) new JSONParser().parse(scanner.next());
  179.         }
  180.     }
  181.  
  182.     /**
  183.      * Private class used to convert the json response to the correct object
  184.      */
  185.     private interface ResponseResolver<R> {
  186.  
  187.         public R resolveResult(JSONObject obj);
  188.     }
  189.     /**
  190.      * Private class used to convert the json response to a uuid
  191.      */
  192.     private static final ResponseResolver<UUID> uuidConverter
  193.             = new ResponseResolver<UUID>() {
  194.  
  195.                 @Override
  196.                 public UUID resolveResult(JSONObject obj) {
  197.                     return UUID.fromString(obj.get("id").toString().replaceAll(
  198.                                     "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
  199.                                     "$1-$2-$3-$4-$5"));
  200.                 }
  201.  
  202.             };
  203.  
  204.     /**
  205.      * Private method used to resolve the queries
  206.      *
  207.      * @param <R> The result of the operation
  208.      * @param <I> The unique input of the operation, this will be used to lookup
  209.      * the cache result
  210.      * @param cacheIndex This is the unique value of the result in the cache
  211.      * @param cache The Map<...> object used to hold the cached values
  212.      * @param callback This object will be called when the request is done
  213.      * @param request Its the job of the <code>RequestResolver</code> to convert
  214.      * the <code>JSONObject</code> back to the object the code needs
  215.      * @param baseUrl The main url used for this operation, this will be passed
  216.      * to <code>String.format</code>
  217.      * @param formats The arguments for <code>String.format</code>
  218.      * @return This method returns a Future<R> to see what the result of the
  219.      * request is
  220.      */
  221.     private <R, I> Future<R> resolve(
  222.             I cacheIndex, Map<I, Pair<FutureTask<R>, List<Callback<R>>>> cache,
  223.             Callback<R> callback, ResponseResolver<R> request,
  224.             String baseUrl, Object... formats) {
  225.         if (!Bukkit.isPrimaryThread()) {
  226.             throw new IllegalStateException(
  227.                     "You need to call this method from the main thread "
  228.                     + "because of the request limits Mojang has configured");
  229.         }
  230.         if (!cache.containsKey(cacheIndex)) {
  231.  
  232.             final List<Callback<R>> callBackList = new LinkedList<>();
  233.             FutureTask<R> task = new FutureTask<R>(new Callable<R>() {
  234.                 @Override
  235.                 public R call() throws Exception {
  236.                     assert !Bukkit.isPrimaryThread();
  237.                     return request.resolveResult(UUIDResolver.this.readJSONFromURL(
  238.                             new URL(String.format(baseUrl, (Object[]) formats))
  239.                     ));
  240.                 }
  241.             }) {
  242.                 @Override
  243.                 protected void done() {
  244.                     FutureTask<R> th = this;
  245.                     new BukkitRunnable() {
  246.  
  247.                         @Override
  248.                         public void run() {
  249.                             assert Bukkit.isPrimaryThread();
  250.                             for (Callback<R> id : callBackList) {
  251.                                 id.onCallBack(th);
  252.                             }
  253.                             callBackList.clear();
  254.                         }
  255.                     }.runTask(plugin);
  256.                 }
  257.             };
  258.             cache.put(cacheIndex, new Pair<>(task, callBackList));
  259.             Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
  260.         }
  261.         assert cache.containsKey(cacheIndex);
  262.         Pair<FutureTask<R>, List<Callback<R>>> result
  263.                 = cache.get(cacheIndex);
  264.         if (result.first.isDone()) {
  265.             if (!result.second.isEmpty()) {
  266.                 for (Callback<R> c : result.second) {
  267.                     c.onCallBack(result.first);
  268.                 }
  269.                 result.second = Collections.emptyList();
  270.             }
  271.             if (callback != null) {
  272.                 callback.onCallBack(result.first);
  273.             }
  274.         } else {
  275.             if (callback != null) {
  276.                 result.second.add(callback);
  277.             }
  278.         }
  279.         return result.first;
  280.     }
  281.  
  282.     /**
  283.      * This class contains a pair of other objects
  284.      * <p>
  285.      * Taken from: <br/>
  286.      * <a href="http://stackoverflow.com/a/677248/1542723">StackOverflow</a>
  287.      *
  288.      * @param <A> first type
  289.      * @param <B> second type
  290.      */
  291.     private static class Pair<A, B> {
  292.  
  293.         private A first;
  294.         private B second;
  295.  
  296.         public Pair(A first, B second) {
  297.             super();
  298.             this.first = first;
  299.             this.second = second;
  300.         }
  301.  
  302.         @Override
  303.         public int hashCode() {
  304.             int hashFirst = first != null ? first.hashCode() : 0;
  305.             int hashSecond = second != null ? second.hashCode() : 0;
  306.  
  307.             return (hashFirst + hashSecond) * hashSecond + hashFirst;
  308.         }
  309.  
  310.         @Override
  311.         public boolean equals(Object other) {
  312.             if (other instanceof Pair) {
  313.                 Pair otherPair = (Pair) other;
  314.                 return ((this.first == otherPair.first
  315.                         || (this.first != null && otherPair.first != null
  316.                         && this.first.equals(otherPair.first)))
  317.                         && (this.second == otherPair.second
  318.                         || (this.second != null && otherPair.second != null
  319.                         && this.second.equals(otherPair.second))));
  320.             }
  321.  
  322.             return false;
  323.         }
  324.  
  325.         @Override
  326.         public String toString() {
  327.             return "(" + first + ", " + second + ")";
  328.         }
  329.  
  330.         public A getFirst() {
  331.             return first;
  332.         }
  333.  
  334.         public void setFirst(A first) {
  335.             this.first = first;
  336.         }
  337.  
  338.         public B getSecond() {
  339.             return second;
  340.         }
  341.  
  342.         public void setSecond(B second) {
  343.             this.second = second;
  344.         }
  345.     }
  346.  
  347.     /**
  348.      * Class used to pass the results back to the parent stack
  349.      *
  350.      * @param <E>
  351.      */
  352.     public interface Callback<E> {
  353.  
  354.         /**
  355.          * This method is called when the lookup is done
  356.          *
  357.          * @param result The result of the operation
  358.          */
  359.         public void onCallBack(FutureTask<E> result);
  360.     }
  361.  
  362. }
Advertisement
Add Comment
Please, Sign In to add comment