package com.bergerkiller.bukkit.common.utils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.zip.ZipEntry; import net.minecraft.server.IPlayerFileData; import net.minecraft.util.com.google.common.base.Charsets; import net.minecraft.util.com.mojang.authlib.GameProfile; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.SimplePluginManager; import com.bergerkiller.bukkit.common.Common; import com.bergerkiller.bukkit.common.StackTraceFilter; import com.bergerkiller.bukkit.common.config.BasicConfiguration; import com.bergerkiller.bukkit.common.conversion.ConversionPairs; import com.bergerkiller.bukkit.common.internal.CommonNMS; import com.bergerkiller.bukkit.common.internal.CommonPlugin; import com.bergerkiller.bukkit.common.reflection.FieldAccessor; import com.bergerkiller.bukkit.common.reflection.SafeField; import com.bergerkiller.bukkit.common.reflection.SafeMethod; public class CommonUtil { public static final int VIEW = Bukkit.getServer().getViewDistance(); public static final int VIEWWIDTH = VIEW + VIEW + 1; public static final int CHUNKAREA = VIEWWIDTH * VIEWWIDTH; public static final int BLOCKVIEW = 32 + (VIEW << 4); public static final Thread MAIN_THREAD = Thread.currentThread(); private static final FieldAccessor<Collection<Plugin>> pluginsField = new SafeField<Collection<Plugin>>(SimplePluginManager.class, "plugins"); /** * Gets a mapping of all commands available on the server, so they can be obtained and executed (dispatched) * * @return Bukkit command map */ public static CommandMap getCommandMap() { return ((CraftServer) Bukkit.getServer()).getCommandMap(); } /** * Sends a message to a sender<br> * - Empty messages are ignored<br> * - Color is stripped from messages to consoles * * @param sender to send to * @param message to send */ public static void sendMessage(Object sender, Object message) { if (message != null) { String msg = message.toString(); if (msg.length() > 0 && sender instanceof CommandSender) { if (!(sender instanceof Player)) { message = ChatColor.stripColor(msg); } for (String line : msg.split("\n", -1)) { ((CommandSender) sender).sendMessage(line); } } } } /** * Sends a message containing a list of items * * @param sender to send to * @param delimiter to use between items * @param items to send */ public static void sendListMessage(Object sender, String delimiter, Object[] items) { String msgpart = null; String item; for (Object oitem : items) { item = oitem.toString(); // display it if (msgpart == null || msgpart.length() + item.length() < 70) { if (msgpart == null) { msgpart = item; } else { msgpart += ChatColor.WHITE + delimiter + item; } } else { sendMessage(sender, msgpart); msgpart = item; } } sendMessage(sender, msgpart); } /** * Calls an Event * * @param event to call * @return the input Event */ public static <T extends Event> T callEvent(T event) { Bukkit.getServer().getPluginManager().callEvent(event); return event; } /** * Checks whether the event handler list of an event has registered listeners<br> * If creating an event causes a performance drain, useless event creation can be avoided this way * * @param handlerList of the Event * @return True if handlers are contained, False if not */ public static boolean hasHandlers(HandlerList handlerList) { return handlerList.getRegisteredListeners().length > 0; } /** * Sets the Player File Data used to load and save player information on the server * * @param playerFileData to set to */ public static void setPlayerFileData(Object playerFileData) { CommonNMS.getPlayerList().playerFileData = (IPlayerFileData) playerFileData; } /** * Saves the specified human information to file * * @param human to save */ public static void savePlayer(HumanEntity human) { CommonNMS.getPlayerList().playerFileData.save(CommonNMS.getNative(human)); } /** * Saves all player information to file */ public static void savePlayers() { CommonNMS.getPlayerList().savePlayers(); } /** * Shuffle an array of type T * * @param array to be shuffled */ public static <T> void shuffle(T[] array) { int random; for (int i = 1; i < array.length; i++) { random = (int) (Math.random() * i); T temp = array[i - 1]; array[i - 1] = array[random]; array[random] = temp; } } /** * Add a Set to another Set * * @param to Set that receives orther set * @param from Set to be added to the orther set * @return new Set */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static Set addAllToSet(Set to, Set from) { for(Object i : from) { to.add(i); } return to; } /** * Gets a list of online players on the server * * @return online players */ public static Collection<Player> getOnlinePlayers() { return ConversionPairs.player.convertAll(CommonNMS.getPlayerList().players); } /** * Checks whether a given command sender has a given permission<br> * Vault is used for permissions if available, otherwise super permissions are used * * @param sender to check * @param permissionNode to check (each part is appended with '.' in between) * @return True if the sender has permission for the node, False if not */ public static boolean hasPermission(CommandSender sender, String[] permissionNode) { return CommonPlugin.getInstance().getPermissionHandler().hasPermission(sender, permissionNode); } /** * Get the game profile from a name. * * @param name to get game profile from. * @return Player's GameProfile, OfflinePlayer profile used if player not found. */ public static GameProfile getGameProfile(String name) { Player player = Bukkit.getPlayer(name); if(player != null) { //The right way return PlayerUtil.getGameProfile(player); } UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)); return new GameProfile(uuid, name); } /** * Checks whether a given command sender has a given permission<br> * Vault is used for permissions if available, otherwise super permissions are used * * @param sender to check * @param permissionNode to check * @return True if the sender has permission for the node, False if not */ public static boolean hasPermission(CommandSender sender, String permissionNode) { return CommonPlugin.getInstance().getPermissionHandler().hasPermission(sender, permissionNode); } /** * Prints the filtered (using the SERVER filter) stack trace of an error * * @param error to print */ public static void printFilteredStackTrace(Throwable error) { StackTraceFilter.SERVER.print(error); } /** * Prints the filtered (using the SERVER filter) stack trace of an error * * @param error to print * @param level of the error */ public static void printFilteredStackTrace(Throwable error, Level level) { StackTraceFilter.SERVER.print(error, level); } /** * Removes all stack trace elements after a given method * * @param elements to filter * @param className to filter from * @param methodName to filter from * @return Filtered stack trace */ public static StackTraceElement[] filterStackTrace(StackTraceElement[] elements, String className, String methodName) { ArrayList<StackTraceElement> rval = new ArrayList<StackTraceElement>(elements.length - 1); for (StackTraceElement elem : elements) { if (elem.getClassName().equals(className) && elem.getMethodName().equals(methodName)) { break; } else { rval.add(elem); } } return rval.toArray(new StackTraceElement[0]); } /** * Checks all plugin files to check if a plugin file exists * * @param name of the plugin * @return Plugin exists? */ public static boolean isPluginInDirectory(String name) { File dir = new File("plugins"); if (!dir.exists()) { return false; } for (File file : dir.listFiles()) { try { BasicConfiguration config = getPluginConfiguration(file, "plugin.yml"); String pluginName = config.get("name", String.class); if (name.equals(pluginName)) { return true; } } catch(IOException e) { } } return false; } /** * Obtains a new YAML Configuration instance for a yaml file contained within a plugin Jar file * * @param pluginJarFile to get a YAML configuration instance of * @param configResourcePath to the configuration resource * @return a new Configuration instance, filled with data from the YAML file * @throws IOException if loading fails or the file can not be found */ public static BasicConfiguration getPluginConfiguration(File pluginJarFile, String configResourcePath) throws IOException { InputStream stream = getPluginResource(pluginJarFile, configResourcePath); try { BasicConfiguration config = new BasicConfiguration(); try { config.loadFromStream(stream); } catch (Throwable t) { throw new IOException("Error in YAML format", t); } return config; } finally { stream.close(); } } /** * Obtains a new InputStream to the resource found in the given Plugin Jar file * * @param pluginJarFile to get a resource of * @param resourcePath to the resource * @return an InputStream to the resource * @throws IOException if loading fails or the file can not be found */ public static InputStream getPluginResource(File pluginJarFile, String resourcePath) throws IOException { // First, find the plugin by Jar file (avoids Jar File decompression times) // Then we can use the ClassLoader of this Jar File to load the resource instead synchronized (Bukkit.getPluginManager()) { for (Plugin plugin : CommonUtil.getPluginsUnsafe()) { File file = getPluginJarFile(plugin); if (pluginJarFile.equals(file)) { InputStream stream = plugin.getResource(resourcePath); if (stream == null) { throw new IOException("Resource not found: " + resourcePath); } return stream; } } } // Not found, stick to reading the JarFile ourselves final JarFile jarFile = new JarFile(pluginJarFile); final ZipEntry entry = jarFile.getEntry(resourcePath); if (entry == null) { jarFile.close(); throw new IOException("Resource not found: " + resourcePath); } return jarFile.getInputStream(entry); } /** * Gets the Jar File where a given Plugin is loaded from. * If the plugin is not in a Jar file, null is returned instead. * * @param plugin to get the Jar File of * @return the Jar File in which the plugin resides, or null if none found */ public static File getPluginJarFile(Plugin plugin) { final Class<?> pluginClass = plugin.getClass(); try { URI uri = pluginClass.getProtectionDomain().getCodeSource().getLocation().toURI(); File file = new File(uri); if (file.exists()) { return file; } } catch (Exception e) { } return null; } /** * Obtains the folder in which plugin-specific information is contained.<br> * Usually this folder is <b>/plugins/[pluginname]</b>.<br> * This method can be used to properly obtain this folder if the plugin is not initialized yet. * * @param plugin to get the data folder of * @return Plugin data folder (never null) */ public static File getPluginDataFolder(Plugin plugin) { File folder = plugin.getDataFolder(); if (folder == null) { File jarFile = getPluginJarFile(plugin); if (jarFile == null) { throw new RuntimeException("Plugin data folder can not be obtained: Not a valid JAR plugin"); } folder = new File(jarFile.getAbsoluteFile().getParentFile(), plugin.getName()); } return folder; } /** * Removes all stack trace elements after a given method from an error * * @param error to filter * @param className to filter from * @param methodName to filter from * @return The input error (uncloned, same instance) */ public static <T extends Throwable> T filterStackTrace(T error, String className, String methodName) { error.setStackTrace(filterStackTrace(error.getStackTrace(), className, methodName)); return error; } /** * Removes all stack trace elements after the method that called this function * * @param elements to filter * @return Filtered stack trace */ public static StackTraceElement[] filterStackTrace(StackTraceElement[] elements) { // Obtain the calling method and class name StackTraceElement[] currStack = Thread.currentThread().getStackTrace(); for (int i = 1; i < currStack.length; i++) { if (!currStack[i].getClassName().equals(CommonUtil.class.getName()) || !currStack[i].getMethodName().equals("filterStackTrace")) { return filterStackTrace(elements, currStack[i + 1].getClassName(), currStack[i + 1].getMethodName()); } } return elements; } /** * Removes all stack trace elements after the method that called this function from an error * * @param error to filter * @return The input error (uncloned, same instance) */ public static <T extends Throwable> T filterStackTrace(T error) { error.setStackTrace(filterStackTrace(error.getStackTrace())); return error; } /** * Performs an unsafe cast to a generic type. Be sure to check that * the object being cast is actually an instance of the type to cast to. * Casting errors will occur while working with the resulting value, * not while performing the casting in this method. * * @param value to cast * @return value cast in an unsafe way */ @SuppressWarnings("unchecked") public static <T> T unsafeCast(Object value) { return (T) value; } /** * Tries to cast the object to the type specified, returning null upon failure * * @param object to cast * @param type to cast to * @return The cast object, or null */ public static <T> T tryCast(Object object, Class<T> type) { return tryCast(object, type, null); } /** * Tries to cast the object to the type specified, returning def upon failure * * @param object to cast * @param type to cast to * @param def to return on cast failure * @return The cast object, or def */ public static <T> T tryCast(Object object, Class<T> type, T def) { if (type.isInstance(object)) { return type.cast(object); } else { return def; } } /** * Schedules a runnable to execute the next Tick<br> * The BKCommonLib internal plugin will handle this task<br> * This method is thread safe * * @param runnable to execute */ public static void nextTick(Runnable runnable) { if (runnable == null) { return; } if (CommonPlugin.hasInstance()) { // Use BKCommonLib next tick task CommonPlugin.getInstance().nextTick(runnable); } else { // Try to find out what plugin this Runnable belongs to Plugin plugin = CommonUtil.getPluginByClass(runnable.getClass()); if (plugin == null) { // Well...ain't that a pickle. // Maybe there is some other plugin we can dump this to? // It's a fallback...it does not have to be fair or perfect! synchronized (Bukkit.getPluginManager()) { Iterator<Plugin> iter = getPluginsUnsafe().iterator(); while (iter.hasNext()) { plugin = iter.next(); if (plugin.isEnabled()) { Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, runnable); return; } } } // Well now we just don't know... CommonPlugin.LOGGER.log(Level.SEVERE, "Unable to properly schedule next-tick task: " + runnable.getClass().getName()); CommonPlugin.LOGGER.log(Level.SEVERE, "The task is executed right away instead...we might recover!"); runnable.run(); } else { // Use the supposed plugin this Class belongs to Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, runnable); } } } /** * Broadcasts a message to all players on a world * * @param message to send */ public static void broadcast(Object message) { if (message != null) { for (Player player : getOnlinePlayers()) { player.sendMessage(message.toString()); } } } /** * Gets all Plugins running on the server WITHOUT allocating a new array if possible. * If there is no performance requirement to avoid array allocation, use {@link #getPlugins()} instead. * Only use this method inside a <b>synchronized</b> body around the Plugin Manager, for example: * <pre> * synchronized (Bukkit.getPluginManager()) { * for (Plugin plugin : CommonUtil.getPluginsUnsafe()) { * System.out.println(plugin.getName()); * } * } * </pre> * * @return unsafe collection of plugins running on the server */ public static Collection<Plugin> getPluginsUnsafe() { final PluginManager man = Bukkit.getPluginManager(); if (man instanceof SimplePluginManager) { return pluginsField.get(man); } else { return Arrays.asList(man.getPlugins()); } } /** * Gets all the Plugins running on the Server * * @return Plugins */ public static Plugin[] getPlugins() { return Bukkit.getPluginManager().getPlugins(); } /** * Checks whether the plugin of the given name is enabled on the Server * * @param name to check * @return True if the plugin is enabled, False if not */ public static boolean isPluginEnabled(String name) { return Bukkit.getPluginManager().isPluginEnabled(name); } /** * Gets a certain Plugin by name * * @param name of the Plugin * @return Plugin */ public static Plugin getPlugin(String name) { return Bukkit.getPluginManager().getPlugin(name); } /** * Gets the plugin by inspecting a Class * * @param clazz * @return the Plugin matching the Class, or null if not found */ public static Plugin getPluginByClass(Class<?> clazz) { ClassLoader loader = clazz.getClassLoader(); synchronized (Bukkit.getServer().getPluginManager()) { for (Plugin plugin : getPluginsUnsafe()) { if (plugin.getClass().getClassLoader() == loader) { return plugin; } } } return null; } /** * Gets the plugin by inspecting the path to a Class * * @param classPath of the Class * @return the Plugin matching the Class, or null if not found */ public static Plugin getPluginByClass(String classPath) { try { return getPluginByClass(Class.forName(classPath)); } catch (ClassNotFoundException e) { return null; } } /** * Obtains the package path of a given Class Path * * @param classPath * @return package path of the Package the class resides in */ public static String getPackagePath(String classPath) { final int idx = classPath.lastIndexOf('.'); return idx == -1 ? "" : classPath.substring(0, idx); } /** * Finds all plugins involved in an array of stack trace elements. * * @param stackTrace to find the plugins for * @return array of plugins mentioned in the Stack Trace */ public static Plugin[] findPlugins(StackTraceElement[] stackTrace) { return findPlugins(Arrays.asList(stackTrace)); } /** * Finds all plugins involved in a list of stack trace elements. * * @param stackTrace to find the plugins for * @return array of plugins mentioned in the Stack Trace */ public static Plugin[] findPlugins(List<StackTraceElement> stackTrace) { LinkedHashSet<Plugin> found = new LinkedHashSet<Plugin>(3); for (StackTraceElement elem : stackTrace) { Plugin plugin = getPluginByClass(elem.getClassName()); if (plugin != null) { found.add(plugin); } } return found.toArray(new Plugin[0]); } /** * Tries to get the class at the path specified and * applies translations based on the server currently running. * * @param path to the class * @return the class, or null if not found */ public static Class<?> getClass(String path) { try { if (Common.SERVER == null) { return Class.forName(path); } else { return Class.forName(Common.SERVER.getClassName(path)); } } catch (ClassNotFoundException e) { return null; } } /** * Tries to get the net.minecraft.server class at the path specified * * @param name of the NMS class * @return the class, or null if not found */ public static Class<?> getNMSClass(String name) { return getClass(Common.NMS_ROOT + "." + name); } /** * Tries to get the org.bukkit.craftbukkit class at the path specified * * @param name of the CB class * @return the class, or null if not found */ public static Class<?> getCBClass(String name) { return getClass(Common.CB_ROOT + "." + name); } /** * Checks whether a plugin is soft-depending on another plugin * * @param plugin * @param depending plugin * @return True if plugin soft-depends on the depending plugin, False if not */ public static boolean isSoftDepending(Plugin plugin, Plugin depending) { final List<String> dep = plugin.getDescription().getSoftDepend(); return !LogicUtil.nullOrEmpty(dep) && dep.contains(depending.getName()); } /** * Checks whether a plugin is depending on another plugin * * @param plugin * @param depending plugin * @return True if plugin depends on the depending plugin, False if not */ public static boolean isDepending(Plugin plugin, Plugin depending) { final List<String> dep = plugin.getDescription().getDepend(); return !LogicUtil.nullOrEmpty(dep) && dep.contains(depending.getName()); } /** * Gets constants of the class type statically defined in the class itself. * If the class is an enum, the enumeration constants are returned. * Otherwise, only the static fields with theClass type are returned. * * @param theClass to get the class constants of * @return class constants defined in class 'theClass' */ public static <T> T[] getClassConstants(Class<T> theClass) { return getClassConstants(theClass, theClass); } /** * Gets constants of the class type statically defined in the class itself. * If the type class is an enum, the enumeration constants are returned. * Otherwise, only the static fields with the same type as the type parameter are returned. * * @param theClass to get the class constants of * @param type of constants to return from theClass * @return class constants defined in class 'theClass' */ @SuppressWarnings("unchecked") public static <T> T[] getClassConstants(Class<?> theClass, Class<T> type) { if (type.isEnum()) { // Get using enum constants return type.getEnumConstants(); } else { // Get using reflection try { Field[] declaredFields = theClass.getDeclaredFields(); ArrayList<T> constants = new ArrayList<T>(declaredFields.length); for (Field field : declaredFields) { if (Modifier.isStatic(field.getModifiers()) && type.isAssignableFrom(field.getType())) { T constant = (T) field.get(null); if (constant != null) { constants.add(constant); } } } return LogicUtil.toArray(constants, type); } catch (Throwable t) { t.printStackTrace(); return LogicUtil.createArray(type, 0); } } } /** * Checks whether a given value is an instance of one of the types specified * * @param value to check * @param types to check against * @return True if value is an instance of one of the types, False if not */ public static boolean isInstance(Object value, Class<?>... types) { for (Class<?> type : types) { if (type.isInstance(value)) { return true; } } return false; } /** * Checks whether the server has officially started running and accepting new players to join. * * @return True if the server started, False if not and the server is still enabling */ public static boolean isServerStarted() { return CommonPlugin.hasInstance() && CommonPlugin.getInstance().isServerStarted(); } /** * Obtains the Handler List of an event, throwing an exception if this is not possible * * @param eventClass to get the HandlerList of * @return the HandlerList * @throws RuntimeException: Event class has no handler list */ public static HandlerList getEventHandlerList(Class<?> eventClass) { try { return (HandlerList) eventClass.getDeclaredMethod("getHandlerList").invoke(null); } catch (Throwable t) { } throw new RuntimeException("Class '" + eventClass.getName() + "' does not have a handler list"); } /** * Re-orders the event registration to ensure that the listener MONITOR handler is called last, after * all other listener handlers have executed. This should only be used if information is changed * that could cause other listeners after this handler to malfunction.<br> * <br> * This method may fail if more than one Monitor handler per Event class is present in a * single Listener. * * @param listener of the event handler * @param eventClass that is handled */ public static void queueListenerLast(Listener listener, Class<?> eventClass) { setListenerOrder(listener, eventClass, false); } /** * Re-orders the event registration to ensure that the listener LOWEST handler is called first, before * all other listener handlers have executed. This should only be used if information is changed * that could cause other listeners before this handler to malfunction.<br> * This method may fail if more than one Lowest handler per Event class is present in a * single Listener. * * @param listener of the event handler * @param eventClass that is handled */ public static void queueListenerFirst(Listener listener, Class<?> eventClass) { setListenerOrder(listener, eventClass, true); } private static void setListenerOrder(Listener listener, Class<?> eventClass, boolean first) { HandlerList list = getEventHandlerList(eventClass); final EventPriority prio = first ? EventPriority.LOWEST : EventPriority.MONITOR; synchronized (list) { EnumMap<EventPriority, ArrayList<RegisteredListener>> handlerSlots = SafeField.get(list, "handlerslots"); ArrayList<RegisteredListener> registeredListenerList = handlerSlots.get(prio); int requestedIndex = first ? 0 : (registeredListenerList.size() - 1); // Try to find the registered listener for (int i = 0; i < registeredListenerList.size(); i++) { RegisteredListener registeredListener = registeredListenerList.get(i); // Check that the Listener matches if (registeredListener.getListener() != listener) { continue; } else if (i == requestedIndex) { // Already in order, do not do anything return; } RegisteredListener[] allListeners = list.getRegisteredListeners(); // Change order in list registeredListenerList.remove(i); registeredListenerList.add(requestedIndex, registeredListener); // Get the index of the listener in the baked array ArrayList<RegisteredListener> newListeners = new ArrayList<RegisteredListener>(allListeners.length); if (first) { newListeners.add(registeredListener); for (RegisteredListener otherListener : allListeners) { if (otherListener != registeredListener) { newListeners.add(otherListener); } } } else { for (RegisteredListener otherListener : allListeners) { if (otherListener != registeredListener) { newListeners.add(otherListener); } } newListeners.add(registeredListener); } // Transfer the listeners over if (newListeners.toArray(allListeners) != allListeners) { // Strange failure, ensure a bake list.bake(); } return; } } } /** * Checks whether a certain method is overrided in a class * * @param baseClass the method is defined in * @param typeInstance to check whether it overrides in the base class * @param methodName of the method * @param parameterTypes of the method * @return True if overrided, False if not */ public static boolean isMethodOverrided(Class<?> baseClass, Object typeInstance, String methodName, Class<?>... parameterTypes) { return isMethodOverrided(baseClass, typeInstance.getClass(), methodName, parameterTypes); } /** * Checks whether a certain method is overrided in a class * * @param baseClass the method is defined in * @param type to check whether it overrides in the base class * @param methodName of the method * @param parameterTypes of the method * @return True if overrided, False if not */ public static boolean isMethodOverrided(Class<?> baseClass, Class<?> type, String methodName, Class<?>... parameterTypes) { return new SafeMethod<Void>(baseClass, methodName, parameterTypes).isOverridedIn(type); } }