Java tutorial
/* * _____ __ _ _ _____ _ * | __|___ ___ _ _ ___ ___| | |_|___| |_| _ | |_ _ ___ * |__ | -_| _| | | -_| _| |__| |_ -| _| __| | | |_ -| * |_____|___|_| \_/|___|_| |_____|_|___|_| |__| |_|___|___| * * ServerListPlus - http://git.io/slp * > The most customizable server status ping plugin for Minecraft! * Copyright (c) 2014, Minecrell <https://github.com/Minecrell> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.minecrell.serverlistplus.bukkit; import net.minecrell.serverlistplus.bukkit.handlers.BukkitEventHandler; import net.minecrell.serverlistplus.bukkit.handlers.ProtocolLibHandler; import net.minecrell.serverlistplus.bukkit.handlers.StatusHandler; import net.minecrell.serverlistplus.core.ServerListPlusCore; import net.minecrell.serverlistplus.core.ServerListPlusException; import net.minecrell.serverlistplus.core.config.CoreConf; import net.minecrell.serverlistplus.core.config.PluginConf; import net.minecrell.serverlistplus.core.config.storage.InstanceStorage; import net.minecrell.serverlistplus.core.favicon.FaviconHelper; import net.minecrell.serverlistplus.core.favicon.FaviconSource; import net.minecrell.serverlistplus.core.logging.JavaServerListPlusLogger; import net.minecrell.serverlistplus.core.logging.ServerListPlusLogger; import net.minecrell.serverlistplus.core.plugin.ScheduledTask; import net.minecrell.serverlistplus.core.plugin.ServerListPlusPlugin; import net.minecrell.serverlistplus.core.plugin.ServerType; import net.minecrell.serverlistplus.core.status.StatusManager; import net.minecrell.serverlistplus.core.status.StatusRequest; import net.minecrell.serverlistplus.core.util.Helper; import net.minecrell.serverlistplus.core.util.Randoms; import java.awt.image.BufferedImage; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Handler; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilderSpec; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.bukkit.ChatColor; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.util.CachedServerIcon; import org.mcstats.MetricsLite; import static net.minecrell.serverlistplus.core.logging.Logger.*; public class BukkitPlugin extends BukkitPluginBase implements ServerListPlusPlugin { private ServerListPlusCore core; private StatusHandler bukkit, protocol; private Listener loginListener, disconnectListener; private MetricsLite metrics; private Method legacy_getOnlinePlayers; // Favicon cache private final CacheLoader<FaviconSource, Optional<CachedServerIcon>> faviconLoader = new CacheLoader<FaviconSource, Optional<CachedServerIcon>>() { @Override public Optional<CachedServerIcon> load(FaviconSource source) throws Exception { // Try loading the favicon BufferedImage image = FaviconHelper.loadSafely(core, source); if (image == null) return Optional.absent(); // Favicon loading failed else return Optional.of(getServer().loadServerIcon(image)); // Success! } }; private LoadingCache<FaviconSource, Optional<CachedServerIcon>> faviconCache; // Request cache private final CacheLoader<InetSocketAddress, StatusRequest> requestLoader = new CacheLoader<InetSocketAddress, StatusRequest>() { @Override public StatusRequest load(InetSocketAddress client) throws Exception { return core.createRequest(client.getAddress()); } }; private LoadingCache<InetSocketAddress, StatusRequest> requestCache; private String requestCacheConf; @Override public void onEnable() { try { Method method = Server.class.getMethod("getOnlinePlayers"); if (method.getReturnType() == Player[].class) legacy_getOnlinePlayers = method; } catch (Throwable ignored) { } this.bukkit = new BukkitEventHandler(this); if (Environment.checkProtocolLib(getServer())) { this.protocol = new ProtocolLibHandler(this); } else getLogger().log(ERROR, "ProtocolLib IS NOT INSTALLED! Most features will NOT work!"); try { // Load the core first this.core = new ServerListPlusCore(this); getLogger().log(INFO, "Successfully loaded!"); } catch (ServerListPlusException e) { getLogger().log(INFO, "Please fix the error before restarting the server!"); disablePlugin(); return; // Disable bukkit to show error in /plugins } catch (Exception e) { getLogger().log(ERROR, "An internal error occurred while loading the core!", e); disablePlugin(); return; // Disable bukkit to show error in /plugins } // Register commands getCommand("serverlistplus").setExecutor(new ServerListPlusCommand()); getLogger().info(getDisplayName() + " enabled."); } @Override public void onDisable() { try { core.stop(); } catch (ServerListPlusException ignored) { } getLogger().info(getDisplayName() + " disabled."); // BungeeCord closes the log handlers automatically, but Bukkit does not for (Handler handler : getLogger().getHandlers()) handler.close(); } // Commands public final class ServerListPlusCommand implements TabExecutor { private ServerListPlusCommand() { } @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { core.executeCommand(new BukkitCommandSender(sender), cmd.getName(), args); return true; } @Override public List<String> onTabComplete(CommandSender sender, Command cmd, String label, String[] args) { return core.tabComplete(new BukkitCommandSender(sender), cmd.getName(), args); } } // Player tracking public final class LoginListener implements Listener { private LoginListener() { } @EventHandler public void onPlayerLogin(AsyncPlayerPreLoginEvent event) { UUID uuid = null; try { uuid = event.getUniqueId(); } catch (NoSuchMethodError ignored) { } core.updateClient(event.getAddress(), uuid, event.getName()); } } public final class OfflineModeLoginListener implements Listener { private OfflineModeLoginListener() { } @EventHandler public void onPlayerLogin(PlayerLoginEvent event) { UUID uuid = null; try { uuid = event.getPlayer().getUniqueId(); } catch (NoSuchMethodError ignored) { } core.updateClient(event.getAddress(), uuid, event.getPlayer().getName()); } } public final class DisconnectListener implements Listener { private DisconnectListener() { } @EventHandler public void onPlayerDisconnect(PlayerQuitEvent event) { UUID uuid = null; try { uuid = event.getPlayer().getUniqueId(); } catch (NoSuchMethodError ignored) { } core.updateClient(event.getPlayer().getAddress().getAddress(), uuid, event.getPlayer().getName()); } } @Override public ServerListPlusCore getCore() { return core; } @Override public ServerType getServerType() { return Environment.getType(); } @Override public String getServerImplementation() { return getServer().getVersion(); } public StatusRequest getRequest(InetSocketAddress client) { return requestCache.getUnchecked(client); } public void requestCompleted(InetSocketAddress client) { requestCache.invalidate(client); } public CachedServerIcon getFavicon(FaviconSource source) { Optional<CachedServerIcon> result = faviconCache.getUnchecked(source); return result.isPresent() ? result.get() : null; } private Collection<? extends Player> getPlayers() { Collection<? extends Player> players; try { // Meh, compatibility players = getServer().getOnlinePlayers(); } catch (NoSuchMethodError e) { try { players = Arrays.asList((Player[]) legacy_getOnlinePlayers.invoke(getServer())); } catch (InvocationTargetException ex) { throw Throwables.propagate(ex.getCause()); } catch (IllegalAccessException ex) { throw Throwables.propagate(ex); } } return players; } @Override public Integer getOnlinePlayers(String location) { World world = getServer().getWorld(location); if (world == null) return null; int count = 0; for (Player player : getPlayers()) { if (player.getWorld().equals(world)) count++; } return count; } @Override public Iterator<String> getRandomPlayers() { Collection<? extends Player> players = getPlayers(); List<String> result = new ArrayList<>(players.size()); for (Player player : players) { result.add(player.getName()); } return Randoms.shuffle(result).iterator(); } @Override public Iterator<String> getRandomPlayers(String worldName) { final World world = getServer().getWorld(worldName); if (world == null) { return null; } Collection<? extends Player> players = getPlayers(); List<String> result = new ArrayList<>(); for (Player player : players) { if (player.getWorld().equals(world)) { result.add(player.getName()); } } if (result.isEmpty()) return null; return Randoms.shuffle(result).iterator(); } @Override public Cache<?, ?> getRequestCache() { return requestCache; } @Override public LoadingCache<FaviconSource, Optional<CachedServerIcon>> getFaviconCache() { return faviconCache; } @Override public void runAsync(Runnable task) { getServer().getScheduler().runTaskAsynchronously(this, task); } @Override public ScheduledTask scheduleAsync(Runnable task, long repeat, TimeUnit unit) { repeat = unit.toMillis(repeat) / 50; return new ScheduledBukkitTask( getServer().getScheduler().runTaskTimerAsynchronously(this, task, repeat, repeat)); } @Override public String colorize(String s) { return ChatColor.translateAlternateColorCodes('&', s); } @Override public ServerListPlusLogger createLogger(ServerListPlusCore core) { return new JavaServerListPlusLogger(core, getLogger()); } @Override public void initialize(ServerListPlusCore core) { } @Override public void reloadCaches(ServerListPlusCore core) { CoreConf conf = core.getConf(CoreConf.class); // Check if request cache configuration has been changed if (requestCacheConf == null || requestCache == null || !requestCacheConf.equals(conf.Caches.Request)) { if (requestCache != null) { // Delete the request cache getLogger().log(DEBUG, "Deleting old request cache due to configuration changes."); requestCache.invalidateAll(); requestCache.cleanUp(); this.requestCache = null; } getLogger().log(DEBUG, "Creating new request cache..."); try { this.requestCacheConf = conf.Caches.Request; this.requestCache = CacheBuilder.from(requestCacheConf).build(requestLoader); } catch (IllegalArgumentException e) { getLogger().log(ERROR, "Unable to create request cache using configuration settings.", e); this.requestCacheConf = core.getDefaultConf(CoreConf.class).Caches.Request; this.requestCache = CacheBuilder.from(requestCacheConf).build(requestLoader); } getLogger().log(DEBUG, "Request cache created."); } } @Override public void reloadFaviconCache(CacheBuilderSpec spec) { if (spec != null) { this.faviconCache = CacheBuilder.from(spec).build(faviconLoader); } else { // Delete favicon cache faviconCache.invalidateAll(); faviconCache.cleanUp(); this.faviconCache = null; } } @Override public void configChanged(InstanceStorage<Object> confs) { // Player tracking if (confs.get(PluginConf.class).PlayerTracking.Enabled) { if (loginListener == null) { registerListener(this.loginListener = Environment.isSpigot() || getServer().getOnlineMode() ? new LoginListener() : new OfflineModeLoginListener()); getLogger().log(DEBUG, "Registered player login listener."); } if (disconnectListener == null) { registerListener(this.disconnectListener = new DisconnectListener()); getLogger().log(DEBUG, "Registered player disconnect listener."); } } else { if (loginListener != null) { unregisterListener(loginListener); this.loginListener = null; getLogger().log(DEBUG, "Unregistered player login listener."); } if (disconnectListener != null) { unregisterListener(disconnectListener); this.disconnectListener = null; getLogger().log(DEBUG, "Unregistered player disconnect listener."); } } // Plugin statistics if (confs.get(PluginConf.class).Stats) { if (metrics == null) try { this.metrics = new MetricsLite(this); metrics.enable(); metrics.start(); } catch (Throwable e) { getLogger().log(DEBUG, "Failed to enable plugin statistics: " + Helper.causedException(e)); } } else if (metrics != null) try { metrics.disable(); this.metrics = null; } catch (Throwable e) { getLogger().log(DEBUG, "Failed to disable plugin statistics: " + Helper.causedException(e)); } } @Override public void statusChanged(StatusManager status) { // Status packet listener if (status.hasChanges()) { if (bukkit.register()) getLogger().log(DEBUG, "Registered ping event handler."); if (protocol == null) getLogger().log(ERROR, "ProtocolLib IS NOT INSTALLED! Most features will NOT work!"); else if (protocol.register()) getLogger().log(DEBUG, "Registered status protocol handler."); } else { if (bukkit.unregister()) getLogger().log(DEBUG, "Unregistered ping event handler."); if (protocol != null && protocol.unregister()) getLogger().log(DEBUG, "Unregistered status protocol handler."); } } }