ferrybig icon

UUIDResolver.java - A bukkit coding project

ferrybig | PRO | 07/28/14 01:48:07 PM UTC | 0 ⭐ | 973 👁️ | Never ⏰ | []
Java |

12.81 KB

|

None

|

0 👍

/

0 👎

/*
 * 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<String, Pair<FutureTask<UUID>, List<Callback<UUID>>>> playerNameCache = new HashMap<>();
 
    public UUID getUUIDAsync(String username) throws InterruptedException, ExecutionException
    {
        return Bukkit.getScheduler().<Future<UUID>>callSyncMethod(plugin, new Callable<Future<UUID>>() {
 
            @Override
            public Future<UUID> 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<UUID> 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<UUID> getUUID(String userName, Callback<UUID> callBack) {
        return this.<UUID, String>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<String, Long>, Pair<FutureTask<UUID>, List<Callback<UUID>>>> 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<UUID> 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<UUID> getUUID(String userName, long unixTime, Callback<UUID> callBack) {
        return this.<UUID, Pair<String, Long>>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<UUID, Pair<FutureTask<JSONObject>, List<Callback<JSONObject>>>> playerDataCache = new HashMap<>();
 
    public JSONObject getInternalDataAsync(UUID uuid) throws InterruptedException, ExecutionException
    {
        return Bukkit.getScheduler().<Future<JSONObject>>callSyncMethod(plugin, new Callable<Future<JSONObject>>() {
 
            @Override
            public Future<JSONObject> call() throws Exception {
                 return getInternalData(uuid);
            }
        }).get().get();
    }
    public Future<JSONObject> 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<JSONObject> getInternalData(UUID userName, Callback<JSONObject> callBack) {
        return this.<JSONObject, UUID>resolve(userName, playerDataCache, callBack,
                new ResponseResolver<JSONObject>() {
 
                    @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<R> {
 
        public R resolveResult(JSONObject obj);
    }
    /**
     * Private class used to convert the json response to a uuid
     */
    private static final ResponseResolver<UUID> uuidConverter
            = new ResponseResolver<UUID>() {
 
                @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 <R> The result of the operation
     * @param <I> 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 <code>RequestResolver</code> to convert
     * the <code>JSONObject</code> back to the object the code needs
     * @param baseUrl The main url used for this operation, this will be passed
     * to <code>String.format</code>
     * @param formats The arguments for <code>String.format</code>
     * @return This method returns a Future<R> to see what the result of the
     * request is
     */
    private <R, I> Future<R> resolve(
            I cacheIndex, Map<I, Pair<FutureTask<R>, List<Callback<R>>>> cache,
            Callback<R> callback, ResponseResolver<R> 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<Callback<R>> callBackList = new LinkedList<>();
            FutureTask<R> task = new FutureTask<R>(new Callable<R>() {
                @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<R> th = this;
                    new BukkitRunnable() {
 
                        @Override
                        public void run() {
                            assert Bukkit.isPrimaryThread();
                            for (Callback<R> 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<FutureTask<R>, List<Callback<R>>> result
                = cache.get(cacheIndex);
        if (result.first.isDone()) {
            if (!result.second.isEmpty()) {
                for (Callback<R> 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
     * <p>
     * Taken from: <br/>
     * <a href="http://stackoverflow.com/a/677248/1542723">StackOverflow</a>
     *
     * @param <A> first type
     * @param <B> second type
     */
    private static class Pair<A, B> {
 
        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 <E>
     */
    public interface Callback<E> {
 
        /**
         * This method is called when the lookup is done
         *
         * @param result The result of the operation
         */
        public void onCallBack(FutureTask<E> result);
    }
 
}

Comments