package com.laytonsmith.core; import com.laytonsmith.PureUtilities.Common.DateUtils; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.SimpleVersion; import com.laytonsmith.PureUtilities.TermColors; import com.laytonsmith.PureUtilities.XMLDocument; import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCConsoleCommandSender; import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLivingEntity; import com.laytonsmith.abstraction.MCMetadatable; import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCPlugin; import com.laytonsmith.abstraction.MCServer; import com.laytonsmith.abstraction.MCVehicle; import com.laytonsmith.abstraction.MCWorld; import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.annotations.typeof; import com.laytonsmith.commandhelper.CommandHelperPlugin; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CByteArray; import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CDouble; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CNumber; import com.laytonsmith.core.constructs.CPrimitive; import com.laytonsmith.core.constructs.CResource; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.NativeTypeList; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.constructs.Variable; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.CRE.CREBadEntityException; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREFormatException; import com.laytonsmith.core.exceptions.CRE.CREIOException; import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; import com.laytonsmith.core.exceptions.CRE.CREInvalidPluginException; import com.laytonsmith.core.exceptions.CRE.CREInvalidWorldException; import com.laytonsmith.core.exceptions.CRE.CRELengthException; import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; import com.laytonsmith.core.exceptions.CRE.CREPlayerOfflineException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.functions.Function; import com.laytonsmith.core.profiler.Profiler; import com.laytonsmith.core.taskmanager.TaskManager; import com.laytonsmith.persistence.DataSourceException; import com.laytonsmith.persistence.PersistenceNetwork; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Array; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class contains several static methods to get various objects that really * should be static in the first place, but aren't. For the most part, when any * code is running, these things will have been initialized, but in the event * they aren't, each function will throw a NotInitializedYetException, which is * a RuntimeException, so you don't have to check for exceptions whenever you * use them. The Exception is caught on a higher level though, so it shouldn't * bubble up too far. * */ public final class Static { private Static() { } private static final Logger logger = Logger.getLogger("CommandHelper"); private static Map<String, String> hostCache = new HashMap<String, String>(); private static final String consoleName = "~console"; private static final String blockPrefix = "#"; // Chosen over @ because that does special things when used by the block /** * In case the API being used doesn't support permission groups, a permission node in the format * <code>String permission = groupPrefix + groupName;</code> * can be assigned to players to declare their permission group. * * Third party APIs may provide better access. */ public static final String groupPrefix = "group."; /** * The label representing unrestricted access. */ public static final String GLOBAL_PERMISSION = "*"; /** * Returns a CArray object from a given construct, throwing a common error * message if not. * * @param construct * @param t * @return */ public static CArray getArray(Construct construct, Target t) { return ArgumentValidation.getArray(construct, t); } /** * Works like the other get* methods, but works in a more generic way for * other types of Constructs. It also assumes that the class specified is * tagged with a typeof annotation, thereby preventing the need for the * expectedClassName like the deprecated version uses. * * @param <T> The type expected. * @param construct The generic object * @param t Code target * @param clazz The type expected. * @return The properly cast object. */ public static <T extends Construct> T getObject(Construct construct, Target t, Class<T> clazz) { return ArgumentValidation.getObject(construct, t, clazz); } /** * Returns a CNumber construct (CInt or CDouble) from any java number. * @param number The java number to convert. * @param t The code target. * @return A construct equivalent to the given java number, whose the type is the better to represent it. */ public static CNumber getNumber(Number number, Target t) { long longValue = number.longValue(); double doubleValue = number.doubleValue(); return longValue == doubleValue ? new CInt(longValue, t) : new CDouble(doubleValue, t); } /** * This function pulls a numerical equivalent from any given construct. It * throws a ConfigRuntimeException if it cannot be converted, for instance * the string "s" cannot be cast to a number. The number returned will * always be a double. * * @param c * @return */ public static double getNumber(Construct c, Target t) { return ArgumentValidation.getNumber(c, t); } /** * Alias to getNumber * * @param c * @return */ public static double getDouble(Construct c, Target t) { return ArgumentValidation.getDouble(c, t); } public static float getDouble32(Construct c, Target t) { return ArgumentValidation.getDouble32(c, t); } /** * Returns an integer from any given construct. * * @param c * @return */ public static long getInt(Construct c, Target t) { return ArgumentValidation.getInt(c, t); } /** * Returns a 32 bit int from the construct. Since the backing value is * actually a long, if the number contained in the construct is not the same * after truncating, an exception is thrown (fail fast). When needing an int * from a construct, this method is much preferred over silently truncating. * * @param c * @param t * @return */ public static int getInt32(Construct c, Target t) { return ArgumentValidation.getInt32(c, t); } /** * Returns a 16 bit int from the construct (a short). Since the backing * value is actually a long, if the number contained in the construct is not * the same after truncating, an exception is thrown (fail fast). When * needing an short from a construct, this method is much preferred over * silently truncating. * * @param c * @param t * @return */ public static short getInt16(Construct c, Target t) { return ArgumentValidation.getInt16(c, t); } /** * Returns an 8 bit int from the construct (a byte). Since the backing value * is actually a long, if the number contained in the construct is not the * same after truncating, an exception is thrown (fail fast). When needing a * byte from a construct, this method is much preferred over silently * truncating. * * @param c * @param t * @return */ public static byte getInt8(Construct c, Target t) { return ArgumentValidation.getInt8(c, t); } /** * Returns a boolean from any given construct. Depending on the type of the * construct being converted, it follows the following rules: If it is an * integer or a double, it is false if 0, true otherwise. If it is a string, * if it is empty, it is false, otherwise it is true. * * @param c * @return */ public static boolean getBoolean(Construct c) { return ArgumentValidation.getBoolean(c, Target.UNKNOWN); } /** * Returns a primitive from any given construct. * @param c * @param t * @return */ public static CPrimitive getPrimitive(Construct c, Target t){ return ArgumentValidation.getObject(c, t, CPrimitive.class); } /** * Returns a CByteArray from any given construct. * @param c * @param t * @return */ public static CByteArray getByteArray(Construct c, Target t) { return ArgumentValidation.getByteArray(c, t); } /** * Returns true if any of the constructs are a CDouble, false otherwise. * * @param c * @return */ public static boolean anyDoubles(Construct... c) { return ArgumentValidation.anyDoubles(c); } /** * Return true if any of the constructs are CStrings, false otherwise. * * @param c * @return */ public static boolean anyStrings(Construct... c) { return ArgumentValidation.anyStrings(c); } /** * Returns true if any of the constructs are CBooleans, false otherwise. * * @param c * @return */ public static boolean anyBooleans(Construct... c) { return ArgumentValidation.anyBooleans(c); } /** * Returns true if any of the constructs are null. * @param c * @return */ public static boolean anyNulls(Construct... c){ return ArgumentValidation.anyNulls(c); } /** * Returns the logger for the plugin * * @return */ public static Logger getLogger() { return logger; } /** * Returns the server for this plugin * * @return * @throws NotInitializedYetException */ public static MCServer getServer() throws NotInitializedYetException { MCServer s = com.laytonsmith.commandhelper.CommandHelperPlugin.myServer; if (s == null) { throw new NotInitializedYetException("The server has not been initialized yet"); } return s; } /** * Gets the reference to the AliasCore for this plugin * * @return * @throws NotInitializedYetException */ public static AliasCore getAliasCore() throws NotInitializedYetException { AliasCore ac = com.laytonsmith.commandhelper.CommandHelperPlugin.getCore(); if (ac == null) { throw new NotInitializedYetException("The core has not been initialized yet"); } return ac; } /** * Gets the current version of the plugin * * @return * @throws NotInitializedYetException */ public static SimpleVersion getVersion() throws NotInitializedYetException { SimpleVersion v = com.laytonsmith.commandhelper.CommandHelperPlugin.version; if (v == null) { throw new NotInitializedYetException("The plugin has not been initialized yet"); } return v; } private static String debugLogFileCurrent = null; private static FileWriter debugLogFileHandle = null; /** * Returns a file that is most likely ready to write to. The timestamp * variables have already been replaced, and parent directories are all * created. * * @return */ public static FileWriter debugLogFile(File root) throws IOException { String currentFileName = root.getPath() + "/" + DateUtils.ParseCalendarNotation(Prefs.DebugLogFile()); if (!currentFileName.equals(debugLogFileCurrent)) { if (debugLogFileHandle != null) { //We're done with the old one, close it. debugLogFileHandle.close(); } debugLogFileCurrent = currentFileName; new File(debugLogFileCurrent).getParentFile().mkdirs(); if (!new File(debugLogFileCurrent).exists()) { new File(debugLogFileCurrent).createNewFile(); } debugLogFileHandle = new FileWriter(currentFileName); } return debugLogFileHandle; } private static String standardLogFileCurrent = null; private static FileWriter standardLogFileHandle = null; public static FileWriter standardLogFile(File root) throws IOException { String currentFileName = root.getPath() + DateUtils.ParseCalendarNotation(Prefs.StandardLogFile()); if (!currentFileName.equals(standardLogFileCurrent)) { if (standardLogFileHandle != null) { //We're done with the old one, close it. standardLogFileHandle.close(); } standardLogFileCurrent = currentFileName; new File(standardLogFileCurrent).getParentFile().mkdirs(); standardLogFileHandle = new FileWriter(currentFileName); } return standardLogFileHandle; } private static String profilingLogFileCurrent = null; private static FileWriter profilingLogFileHandle = null; public static FileWriter profilingLogFile(File root) throws IOException { String currentFileName = root.getPath() + DateUtils.ParseCalendarNotation(Prefs.ProfilingFile()); if (!currentFileName.equals(profilingLogFileCurrent)) { if (profilingLogFileHandle != null) { //We're done with the old one, close it. profilingLogFileHandle.close(); } profilingLogFileCurrent = currentFileName; new File(profilingLogFileCurrent).getParentFile().mkdirs(); profilingLogFileHandle = new FileWriter(currentFileName); } return profilingLogFileHandle; } public static void checkPlugin(String name, Target t) throws ConfigRuntimeException { if (Static.getServer().getPluginManager().getPlugin(name) == null) { throw new CREInvalidPluginException("Needed plugin " + name + " not found!", t); } } /** * Regex patterns */ private static final Pattern INVALID_HEX = Pattern.compile("0x[a-fA-F0-9]*[^a-fA-F0-9]+[a-fA-F0-9]*"); private static final Pattern VALID_HEX = Pattern.compile("0x[a-fA-F0-9]+"); private static final Pattern INVALID_BINARY = Pattern.compile("\"0b[0-1]*[^0-1]+[0-1]*\""); private static final Pattern VALID_BINARY = Pattern.compile("0b[0-1]+"); private static final Pattern INVALID_OCTAL = Pattern.compile("0o[0-7]*[^0-7]+[0-7]*"); private static final Pattern VALID_OCTAL = Pattern.compile("0o[0-7]+"); /** * Given a string input, creates and returns a Construct of the appropriate * type. This takes into account that null, true, and false are keywords. * * @param val * @param t * @return * @throws ConfigRuntimeException If the value is a hex or binary value, but * has invalid characters in it. */ public static Construct resolveConstruct(String val, Target t) throws ConfigRuntimeException { if (val == null) { return new CString("", t); } if(val.equals("true")){ return CBoolean.TRUE; } if(val.equals("false")){ return CBoolean.FALSE; } if(val.equals("null")){ return CNull.NULL; } if(val.equals("void")){ return CClassType.VOID; } if (INVALID_HEX.matcher(val).matches()) { throw new CREFormatException("Hex numbers must only contain digits 0-9, and the letters A-F, but \"" + val + "\" was found.", t); } if (VALID_HEX.matcher(val).matches()) { //Hex number return new CInt(Long.parseLong(val.substring(2), 16), t); } if (INVALID_BINARY.matcher(val).matches()) { throw new CREFormatException("Binary numbers must only contain digits 0 and 1, but \"" + val + "\" was found.", t); } if (VALID_BINARY.matcher(val).matches()) { //Binary number return new CInt(Long.parseLong(val.substring(2), 2), t); } if (INVALID_OCTAL.matcher(val).matches()){ throw new CREFormatException("Octal numbers must only contain digits 0-7, but \"" + val + "\" was found.", t); } if (VALID_OCTAL.matcher(val).matches()){ return new CInt(Long.parseLong(val.substring(2), 8), t); } try { return new CInt(Long.parseLong(val), t); } catch (NumberFormatException e) { try { if (!(val.contains(" ") || val.contains("\t"))) { //Interesting behavior in Double.parseDouble causes it to "trim" strings first, then //try to parse them, which is not desireable in our case. So, if the value contains //any characters other than [\-0-9\.], we want to make it a string instead return new CDouble(Double.parseDouble(val), t); } } catch (NumberFormatException g) { // Not a double either } } // TODO: Once compiler environments are added, we would need to check to see if the value here is a custom // type. However, as it stands, since we only support the native types, we will just hardcode the check here. if(NativeTypeList.getNativeTypeList().contains(val)){ return new CClassType(val, t); } else { return new CString(val, t); } } public static Construct resolveDollarVar(Construct variable, List<Variable> vars) { if (variable == null) { return CNull.NULL; } if (variable.getCType() == Construct.ConstructType.VARIABLE) { for (Variable var : vars) { if (var.getVariableName().equals(((Variable) variable).getVariableName())) { return new CString(var.val(), var.getTarget()); } } return new CString(((Variable) variable).getDefault(), variable.getTarget()); } else { return variable; } } /** * This function sends a message to the player. If the player is not online, * a CRE is thrown. * * @param m * @param msg */ public static void SendMessage(final MCCommandSender m, String msg, final Target t) { if (m != null && !(m instanceof MCConsoleCommandSender)) { if (m instanceof MCPlayer) { MCPlayer p = (MCPlayer) m; if (!p.isOnline()) { throw new CREPlayerOfflineException("The player " + p.getName() + " is not online", t); } } m.sendMessage(msg); } else { msg = Static.MCToANSIColors(msg); if (msg.contains("\033")) { //We have terminal colors, we need to reset them at the end msg += TermColors.reset(); } StreamUtils.GetSystemOut().println(msg); } } /** * Works like * {@link #SendMessage(com.laytonsmith.abstraction.MCCommandSender, java.lang.String, com.laytonsmith.core.constructs.Target)} * except it doesn't require a target, and ignores the message if the * command sender is offline. * * @param m * @param msg */ public static void SendMessage(final MCCommandSender m, String msg) { try { SendMessage(m, msg, Target.UNKNOWN); } catch (ConfigRuntimeException e) { //Ignored } } /** * Returns the name set aside to identify console via string<br> * This is done here so that if it ever changes, it will update in all * functions/docs * * @return */ public static String getConsoleName() { return consoleName; } /** * Returns the string set aside to prefix block names to distinguish them * from players * * @return */ public static String getBlockPrefix() { return blockPrefix; } /** * Returns an item stack from the given item notation. Defaulting to the * specified qty, this will throw an exception if the notation is invalid. * * @param functionName * @param notation * @param qty * @throws ConfigRuntimeException FormatException if the notation is * invalid. * @return */ public static MCItemStack ParseItemNotation(String functionName, String notation, int qty, Target t) { int type = 0; short data = 0; MCItemStack is; String[] sData = notation.split(":"); try { type = Integer.parseInt(sData[0]); if (sData.length > 1) { data = (short) Integer.parseInt(sData[1]); } } catch (NumberFormatException e) { throw new CREFormatException("Item value passed to " + functionName + " is invalid: " + notation, t); } is = StaticLayer.GetItemStack(type, qty); is.setDurability(data); //is.setData(new MaterialData(type, data)); return is; } /** * Works in reverse from the other ParseItemNotation * * @param is * @return */ public static String ParseItemNotation(MCItemStack is) { if (is == null) { return "0"; } String append = null; if (is.getDurability() != 0) { append = Short.toString(is.getDurability()); } else if (is.getData() != null) { append = Integer.toString(is.getData().getData()); } return is.getTypeId() + (append == null ? "" : ":" + append); } public static String ParseItemNotation(MCBlock b) { if (b == null || b.isNull()) { return "0"; } return b.getTypeId() + (b.getData() == 0 ? "" : ":" + Byte.toString(b.getData())); } private static Map<String, MCCommandSender> injectedPlayers = new HashMap<String, MCCommandSender>(); private static final Pattern DASHLESS_PATTERN = Pattern.compile("^([A-Fa-f0-9]{8})([A-Fa-f0-9]{4})([A-Fa-f0-9]{4})([A-Fa-f0-9]{4})([A-Fa-f0-9]{12})$"); /** * Based on https://github.com/sk89q/SquirrelID * * @param subject * @param t * @return */ public static UUID GetUUID(String subject, Target t) { try { if (subject.length() == 36) { return UUID.fromString(subject); } if (subject.length() == 32) { Matcher matcher = DASHLESS_PATTERN.matcher(subject); if (!matcher.matches()) { throw new IllegalArgumentException("Invalid UUID format."); } return UUID.fromString(matcher.replaceAll("$1-$2-$3-$4-$5")); } else { throw new CRELengthException("A UUID is expected to be 32 or 36 characters," + " but the given string was " + subject.length() + " characters.", t); } } catch (IllegalArgumentException iae) { throw new CREIllegalArgumentException("A UUID length string was given, but was not a valid UUID.", t); } } public static UUID GetUUID(Construct subject, Target t) { return GetUUID(subject.val(), t); } public static MCOfflinePlayer GetUser(Construct search, Target t) { return GetUser(search.val(), t); } /** * Provides a user object containing info that doesn't require an online player. * If provided a string between 1 and 16 characters, the lookup will be name-based. * If provided a string that is 32 or 36 characters, the lookup will be uuid-based. * * @param search The text to be searched, can be between 1 and 16 characters, or 32 or 36 characters * @param t * @return */ public static MCOfflinePlayer GetUser(String search, Target t) { MCOfflinePlayer ofp; if (search.length() > 0 && search.length() <= 16) { ofp = getServer().getOfflinePlayer(search); } else { try { ofp = getServer().getOfflinePlayer(GetUUID(search, t)); } catch (ConfigRuntimeException cre) { if (cre instanceof CRELengthException) { throw new CRELengthException("The given string was the wrong size to identify a player." + " A player name is expected to be between 1 and 16 characters. " + cre.getMessage(), t); } else { throw cre; } } } return ofp; } /** * Returns the player specified by name. Injected players also are returned in this list. * If provided a string between 1 and 16 characters, the lookup will be name-based. * If provided a string that is 32 or 36 characters, the lookup will be uuid-based. * * @param player * @param t * @return * @throws ConfigRuntimeException */ public static MCPlayer GetPlayer(String player, Target t) throws ConfigRuntimeException { MCCommandSender m; if (player == null) { throw new CREPlayerOfflineException("No player was specified!", t); } if (player.length() > 0 && player.length() <= 16) { m = GetCommandSender(player, t); } else { try { m = getServer().getPlayer(GetUUID(player, t)); } catch (ConfigRuntimeException cre) { if (cre instanceof CRELengthException) { throw new CRELengthException("The given string was the wrong size to identify a player." + " A player name is expected to be between 1 and 16 characters. " + cre.getMessage(), t); } else { throw cre; } } } if (m == null) { throw new CREPlayerOfflineException("The specified player (" + player + ") is not online", t); } if (!(m instanceof MCPlayer)) { throw new CREPlayerOfflineException("Expecting a player name, but \"" + player + "\" was found.", t); } MCPlayer p = (MCPlayer) m; if (!p.isOnline()) { throw new CREPlayerOfflineException("The specified player (" + player + ") is not online", t); } return p; } /** * Returns the specified command sender. Players are supported, as is the * special ~console user. The special ~console user will always return a * user. * * @param player * @param t * @return * @throws ConfigRuntimeException */ public static MCCommandSender GetCommandSender(String player, Target t) throws ConfigRuntimeException { MCCommandSender m = null; if (injectedPlayers.containsKey(player)) { m = injectedPlayers.get(player); } else { if (consoleName.equals(player)) { m = Static.getServer().getConsole(); } else { try { m = Static.getServer().getPlayer(player); } catch (Exception e) { //Apparently the server can occasionally throw exceptions here, so instead of rethrowing //a NPE or whatever, we'll assume that the player just isn't online, and //throw a CRE instead. } } } if (m == null || (m instanceof MCPlayer && (!((MCPlayer) m).isOnline() && !injectedPlayers.containsKey(player)))) { throw new CREPlayerOfflineException("The specified player (" + player + ") is not online", t); } return m; } public static MCPlayer GetPlayer(Construct player, Target t) throws ConfigRuntimeException { return GetPlayer(player.val(), t); } /** * If the sender is a player, it is returned, otherwise a * ConfigRuntimeException is thrown. * * @param environment * @param t * @return */ public static MCPlayer getPlayer(Environment environment, Target t) { MCPlayer player = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); if (player != null) { return player; } else { throw new CREPlayerOfflineException("The passed arguments induce that the function must be run by a player.", t); } } public static boolean isNull(Construct construct) { return construct instanceof CNull; } public static int Normalize(int i, int min, int max) { return java.lang.Math.min(max, java.lang.Math.max(min, i)); } /** * Returns the entity with the specified id. If it doesn't exist, a * ConfigRuntimeException is thrown. * * @param id * @return */ public static MCEntity getEntity(int id, Target t) { for (MCWorld w : getServer().getWorlds()) { for (MCEntity e : w.getEntities()) { if (e.getEntityId() == id) { return StaticLayer.GetCorrectEntity(e); } } } throw new CREBadEntityException("That entity (ID " + id + ") does not exist.", t); } public static MCEntity getEntity(Construct id, Target t) { return getEntityByUuid(GetUUID(id.val(), t), t); } public static MCLivingEntity getLivingEntity(Construct id, Target t) { return getLivingByUUID(GetUUID(id.val(), t), t); } /** * Returns the entity with the specified unique id. If it doesn't exist, a * ConfigRuntimeException is thrown. * * @param id * @return */ public static MCEntity getEntityByUuid(UUID id, Target t) { for (MCWorld w : getServer().getWorlds()) { for (MCEntity e : w.getEntities()) { if (e.getUniqueId().compareTo(id) == 0) { return StaticLayer.GetCorrectEntity(e); } } } throw new CREBadEntityException("That entity (UUID " + id + ") does not exist.", t); } /** * Returns the living entity with the specified unique id. If it doesn't exist or * isn't living, a ConfigRuntimeException is thrown. * * @param id * @return */ public static MCLivingEntity getLivingByUUID(UUID id, Target t) { for (MCWorld w : Static.getServer().getWorlds()) { for (MCLivingEntity e : w.getLivingEntities()) { if (e.getUniqueId().compareTo(id) == 0) { try { return (MCLivingEntity) StaticLayer.GetCorrectEntity(e); } catch (ClassCastException cce) { throw new CREBadEntityException("The entity found was misinterpreted by the converter, this is" + " a developer mistake, please file a ticket.", t); } } } } throw new CREBadEntityException("That entity (" + id + ") does not exist or is not alive.", t); } /** * Returns the living entity with the specified id. If it doesn't exist or isn't living, a ConfigRuntimeException is * thrown. * * @param id * * @return */ public static MCLivingEntity getLivingEntity(int id, Target t) { for (MCWorld w : Static.getServer().getWorlds()) { for (MCLivingEntity e : w.getLivingEntities()) { if (e.getEntityId() == id) { try { return (MCLivingEntity) StaticLayer.GetCorrectEntity(e); } catch (ClassCastException cce) { throw new CREBadEntityException("The entity found was misinterpreted by the converter, this is" + " a developer mistake, please file a ticket.", t); } } } } throw new CREBadEntityException("That entity (" + id + ") does not exist or is not alive.", t); } /** * Returns all vehicles from all maps. * * @return */ public static List<MCVehicle> getVehicles() { List<MCVehicle> vehicles = new ArrayList<MCVehicle>(); for (MCWorld w : Static.getServer().getWorlds()) { for (MCEntity e : w.getEntities()) { MCEntity entity = StaticLayer.GetCorrectEntity(e); if (entity instanceof MCVehicle) { vehicles.add((MCVehicle) entity); } } } return vehicles; } /** * Returns the world with the specified name. If it does not exist, a * ConfigRuntimeException is thrown. * * @param name * @param t * @return */ public static MCWorld getWorld(String name, Target t) { MCWorld world = getServer().getWorld(name); if (world != null) { return world; } else { throw new CREInvalidWorldException("Unknown world:" + name + ".", t); } } /** * Returns the world with the specified name. If it does not exist, a * ConfigRuntimeException is thrown. * * @param name * @param t * @return */ public static MCWorld getWorld(Construct name, Target t) { return getWorld(name.val(), t); } /** * Returns the plugin with the specified name. If it does not exist, a * ConfigRuntimeException is thrown. * * @param name * @param t * @return */ public static MCPlugin getPlugin(String name, Target t) { MCPlugin plugin = getServer().getPluginManager().getPlugin(name); if (plugin != null) { return plugin; } else { throw new CREInvalidPluginException("Unknown plugin:" + name + ".", t); } } public static MCPlugin getPlugin(Construct name, Target t) { return getPlugin(name.val(), t); } /** * Returns the metadatable object designated by the given construct. If the * construct is invalid or if the object does not exist, a * ConfigRuntimeException is thrown. * * @param construct * @param t * @return */ public static MCMetadatable getMetadatable(Construct construct, Target t) { if (construct instanceof CInt) { return Static.getEntity(construct, t); } else if (construct instanceof CArray) { return ObjectGenerator.GetGenerator().location(construct, null, t).getBlock(); } else if (construct instanceof CString) { switch (construct.val().length()) { case 32: case 36: return Static.getEntity(construct, t); default: return Static.getWorld(construct, t); } } else { throw new CRECastException("An array or a string was expected, but " + construct.val() + " was found.", t); } } public static String strJoin(Collection c, String inner) { StringBuilder b = new StringBuilder(); Object[] o = c.toArray(); for (int i = 0; i < o.length; i++) { if (i != 0) { b.append(inner); } b.append(o[i]); } return b.toString(); } public static String strJoin(Object[] o, String inner) { StringBuilder b = new StringBuilder(); for (int i = 0; i < o.length; i++) { if (i != 0) { b.append(inner); } b.append(o[i]); } return b.toString(); } /** * Returns the system based line seperator character * * @return */ public static String LF() { return System.getProperty("line.separator"); } public static void LogDebug(File root, String message) throws IOException { LogDebug(root, message, LogLevel.OFF); } /** * Equivalent to LogDebug(root, message, level, true); */ public static synchronized void LogDebug(File root, String message, LogLevel level) throws IOException { LogDebug(root, message, level, true); } /** * Logs an error message, depending on the log level of the message and the * user's preferences. * * @param root * @param message * @param level * @param printScreen If true, the message (if otherwise shown) will be * printed to the screen. If false, it never will be, though it will still * be logged to the log file. * @throws IOException */ public static synchronized void LogDebug(File root, String message, LogLevel level, boolean printScreen) throws IOException { //If debug mode is on in the prefs, we want to log this to the screen too if (Prefs.DebugMode() || Prefs.ShowWarnings() || level == LogLevel.ERROR) { String color = ""; Level lev = Level.INFO; boolean show = false; switch (level) { case ERROR: color = TermColors.RED; lev = Level.SEVERE; show = true; break; case WARNING: color = TermColors.YELLOW; lev = Level.WARNING; if (Prefs.DebugMode() || Prefs.ShowWarnings()) { show = true; } break; case INFO: color = TermColors.GREEN; lev = Level.INFO; if (Prefs.DebugMode()) { show = true; } break; case DEBUG: color = TermColors.BRIGHT_BLUE; lev = Level.INFO; if (Prefs.DebugMode()) { show = true; } break; case VERBOSE: color = TermColors.WHITE; lev = Level.INFO; if (Prefs.DebugMode()) { show = true; } break; } if (show && printScreen) { Static.getLogger().log(lev, "{0}{1}{2}", new Object[]{color, message, TermColors.reset()}); } } String timestamp = DateUtils.ParseCalendarNotation("%Y-%M-%D %h:%m.%s - "); QuickAppend(Static.debugLogFile(root), timestamp + message + Static.LF()); } public static void QuickAppend(FileWriter f, String message) throws IOException { f.append(message); f.flush(); } public static boolean hasCHPermission(String functionName, Environment env) { //The * label completely overrides everything if (GLOBAL_PERMISSION.equals(env.getEnv(GlobalEnv.class).GetLabel())) { return true; } MCPlayer player = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCCommandSender commandSender = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); String label = env.getEnv(GlobalEnv.class).GetLabel(); boolean perm = false; if (commandSender != null) { if (commandSender.isOp()) { perm = true; } else if (commandSender instanceof MCPlayer) { perm = player.hasPermission("ch.func.use." + functionName) || player.hasPermission("commandhelper.func.use." + functionName); if (label != null && label.startsWith("~")) { String[] groups = label.substring(1).split("/"); for (String group : groups) { if (player.inGroup(group)) { perm = true; break; } } } else { if (label != null) { if (label.contains(".")) { //We are using a non-standard permission. Don't automatically //add CH's prefix if (player.hasPermission(label)) { perm = true; } } else if ((player.hasPermission("ch.alias." + label)) || player.hasPermission("commandhelper.alias." + label)) { perm = true; } } } } else if (commandSender instanceof MCConsoleCommandSender) { perm = true; } } else { perm = true; } return perm; } public static String Logo() { String logo = Installer.parseISToString(Static.class.getResourceAsStream("/mainlogo")); logo = logo.replaceAll("( +)", TermColors.BG_BLACK + "$1"); logo = logo.replaceAll("_", TermColors.BG_RED + TermColors.RED + "_"); logo = logo.replaceAll("/", TermColors.BG_BRIGHT_WHITE + TermColors.WHITE + "/"); String s = logo + TermColors.reset(); return s; } public static String DataManagerLogo() { String logo = Installer.parseISToString(Static.class.getResourceAsStream("/datamanagerlogo")); logo = logo.replaceAll("( +)", TermColors.BG_BLACK + "$1"); logo = logo.replaceAll("_", TermColors.CYAN + TermColors.BG_CYAN + "_"); logo = logo.replaceAll("/", TermColors.BG_WHITE + TermColors.WHITE + "/"); String s = logo + TermColors.reset(); return s; } public static String GetStringResource(String name) { return GetStringResource(Static.class, name); } public static String GetStringResource(Class path, String name) { return Installer.parseISToString(path.getResourceAsStream(name)); } /** * Pulls out the MCChatColors from the string, and replaces them with the * nearest match ANSI terminal color. * * @param mes If null, simply returns null * @return */ public static String MCToANSIColors(String mes) { //Pull out the MC colors if (mes == null) { return null; } return mes .replaceAll("§0", TermColors.BLACK) .replaceAll("§1", TermColors.BLUE) .replaceAll("§2", TermColors.GREEN) .replaceAll("§3", TermColors.CYAN) .replaceAll("§4", TermColors.RED) .replaceAll("§5", TermColors.MAGENTA) .replaceAll("§6", TermColors.YELLOW) .replaceAll("§7", TermColors.WHITE) .replaceAll("§8", TermColors.BRIGHT_BLACK) .replaceAll("§9", TermColors.BRIGHT_BLUE) .replaceAll("§a", TermColors.BRIGHT_GREEN) .replaceAll("§b", TermColors.BRIGHT_CYAN) .replaceAll("§c", TermColors.BRIGHT_RED) .replaceAll("§d", TermColors.BRIGHT_MAGENTA) .replaceAll("§e", TermColors.BRIGHT_YELLOW) .replaceAll("§f", TermColors.BRIGHT_WHITE) .replaceAll("§k", "") //Uh, no equivalent for "random" .replaceAll("§l", TermColors.BOLD) .replaceAll("§m", TermColors.STRIKE) .replaceAll("§n", TermColors.UNDERLINE) .replaceAll("§o", TermColors.ITALIC) .replaceAll("§r", TermColors.RESET); } public static void InjectPlayer(MCCommandSender player) { String name = player.getName(); if ("CONSOLE".equals(name)) { name = "~console"; } injectedPlayers.put(name, player); } /** * Removes a player into the global player proxy system. Returns the player * removed (or null if none were injected). * * @param player * @return */ public static MCCommandSender UninjectPlayer(MCCommandSender player) { String name = player.getName(); if ("CONSOLE".equals(name)) { name = "~console"; } return injectedPlayers.remove(name); } public static void HostnameCache(final String name, final InetSocketAddress address) { CommandHelperPlugin.hostnameLookupThreadPool.submit(new Runnable() { @Override public void run() { CommandHelperPlugin.hostnameLookupCache.put(name, address.getHostName()); } }); } public static void SetPlayerHost(String playerName, String host) { hostCache.put(playerName, host); } public static String GetHost(MCPlayer p) { return hostCache.get(p.getName()); } public static void AssertPlayerNonNull(MCPlayer p, Target t) throws ConfigRuntimeException { if (p == null) { throw new CREPlayerOfflineException("No player was specified!", t); } assert p != null; } public static long msToTicks(long ms) { return ms / 50; } public static long ticksToMs(long ticks) { return ticks * 50; } public static void AssertNonNull(Object var, String message) throws NullPointerException { if (var == null) { throw new NullPointerException(message); } } /** * Generates a new environment, assuming that the jar has a folder next to * it named CommandHelper, and that folder is the root. * * @return * @throws IOException * @throws DataSourceException * @throws URISyntaxException */ public static Environment GenerateStandaloneEnvironment() throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { File platformFolder = MethodScriptFileLocations.getDefault().getConfigDirectory(); Installer.Install(platformFolder); ConnectionMixinFactory.ConnectionMixinOptions options = new ConnectionMixinFactory.ConnectionMixinOptions(); options.setWorkingDirectory(platformFolder); PersistenceNetwork persistenceNetwork = new PersistenceNetwork(MethodScriptFileLocations.getDefault().getPersistenceConfig(), new URI("sqlite://" + new File(platformFolder, "persistence.db").getCanonicalPath().replace('\\', '/')), options); GlobalEnv gEnv = new GlobalEnv(new MethodScriptExecutionQueue("MethodScriptExecutionQueue", "default"), new Profiler(MethodScriptFileLocations.getDefault().getProfilerConfigFile()), persistenceNetwork, platformFolder, new Profiles(MethodScriptFileLocations.getDefault().getProfilesFile()), new TaskManager()); gEnv.SetLabel(GLOBAL_PERMISSION); return Environment.createEnvironment(gEnv, new CommandHelperEnvironment()); } /** * Asserts that all the args are not CNulls. If so, throws a * ConfigRuntimeNullPointerException * * @param t * @param args * @throws ConfigRuntimeException */ public static void AssertNonCNull(Target t, Construct... args) throws ConfigRuntimeException { for (Construct arg : args) { if (arg instanceof CNull) { throw new CRENullPointerException("Argument was null, and nulls are not allowed.", t); } } } public static String GetStacktraceString(Throwable t) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); return sw.toString(); } /** * Returns the actual file location, given the script's partial (or * absolute) file path, and depending on the context, the correct File * object. Security checking is not done at this stage, this merely * transforms the path into the correct File object. Additionally, if arg is * null, then the default is returned. If it is known that the arg won't * ever be null, null may be set as the default. Except in cases where both * arg and def are null, this function will never return null. If the arg * starts with ~, it is replaced with the user's home directory, as defined * by the system property user.home. * * This generally condenses a 5 or 6 line operation into 1 line. * * @param arg * @return */ public static File GetFileFromArgument(String arg, Environment env, Target t, File def) throws ConfigRuntimeException { if (arg == null) { return def; } if (arg.startsWith("~")) { arg = System.getProperty("user.home") + arg.substring(1); } File f = new File(arg); if (f.isAbsolute()) { return f; } //Ok, it's not absolute, so we need to see if we're in cmdline mode or not. //If so, we use the root directory, not the target. if (env != null && InCmdLine(env)) { return new File(env.getEnv(GlobalEnv.class).GetRootFolder(), arg); } else if (t.file() == null) { throw new CREIOException("Unable to receive a non-absolute file with an unknown target", t); } else { return new File(t.file().getParent(), arg); } } /** * Returns true if currently running in cmdline mode. * * @param environment * @return */ public static boolean InCmdLine(Environment environment) { return environment.getEnv(GlobalEnv.class).GetCustom("cmdline") instanceof Boolean && (Boolean) environment.getEnv(GlobalEnv.class).GetCustom("cmdline"); } /** * This verifies that the type required is actually present, and returns the * value, cast to the appropriate type, or, if not the correct type, a CRE. * <p> * Note that this does not do type coersion, and therefore does not work on * primitives, and is only meant for arrays, closures, and other complex * types. * * @param <T> The type desired to be cast to * @param type The type desired to be cast to * @param args The array of arguments. * @param argNumber The argument number, used both for grabbing the correct * argument from args, and building the error message if the cast cannot * occur. * @param func The function, in case this errors out, to build the error * message. * @param t The code target * @return The value, cast to the desired type. */ public static <T extends Construct> T AssertType(Class<T> type, Construct[] args, int argNumber, Function func, Target t) { Construct value = args[argNumber]; if (!type.isAssignableFrom(value.getClass())) { typeof todesired = type.getAnnotation(typeof.class); String toactual = value.typeof(); if (todesired != null) { throw new CRECastException("Argument " + (argNumber + 1) + " of " + func.getName() + " was expected to be a " + todesired.value() + ", but " + toactual + " \"" + value.val() + "\" was found.", t); } else { //If the typeof annotation isn't present, this is a programming error. throw new IllegalArgumentException(""); } } else { return (T) value; } } /** * Given a java object, returns a MethodScript object. * * @param object * @param t * @return */ public static Construct getMSObject(Object object, Target t) { if (object == null) { return CNull.NULL; } else if (object instanceof Boolean) { return CBoolean.get((boolean) object); } else if ((object instanceof Byte) || (object instanceof Short) || (object instanceof Integer) || (object instanceof Long)) { return new CInt((long) object, t); } else if ((object instanceof Float) || (object instanceof Double)) { return new CDouble((double) object, t); } else if (object instanceof Character) { return new CString((char) object, t); } else if (object instanceof String) { return new CString((String) object, t); } else if (object instanceof StringBuffer) { return new CResource<>((StringBuffer) object, new CResource.ResourceToString() { @Override public String getString(CResource res) { return res.getResource().toString(); } }, t); } else if (object instanceof XMLDocument) { return new CResource<>((XMLDocument) object, t); } else if (object instanceof Construct) { return (Construct) object; } else if (object instanceof boolean[]) { boolean[] array = (boolean[]) object; CArray r = new CArray(t); for (boolean b : array) { r.push(CBoolean.get(b), t); } return r; } else if (object instanceof byte[]) { return CByteArray.wrap((byte[]) object, t); } else if (object instanceof char[]) { char[] array = (char[]) object; CArray r = new CArray(t); for (char c : array) { r.push(new CString(c, t), t); } return r; } else if (object instanceof short[]) { short[] array = (short[]) object; CArray r = new CArray(t); for (short s : array) { r.push(new CInt(s, t), t); } return r; } else if (object instanceof int[]) { int[] array = (int[]) object; CArray r = new CArray(t); for (int i : array) { r.push(new CInt(i, t), t); } return r; } else if (object instanceof long[]) { long[] array = (long[]) object; CArray r = new CArray(t); for (long l : array) { r.push(new CInt(l, t), t); } return r; } else if (object instanceof float[]) { float[] array = (float[]) object; CArray r = new CArray(t); for (float f : array) { r.push(new CDouble(f, t), t); } return r; } else if (object instanceof double[]) { double[] array = (double[]) object; CArray r = new CArray(t); for (double d : array) { r.push(new CDouble(d, t), t); } return r; } else if (object instanceof Object[]) { CArray r = new CArray(t); for (Object o : (Object[]) object) { r.push((o == object) ? r : getMSObject(o, t), t); } return r; } else if (object instanceof Collection) { return getMSObject(((Collection) object).toArray(), t); } else if (object instanceof Map) { Map map = ((Map) object); CArray r = new CArray(t); for (Object key : map.keySet()) { Object o = map.get(key); r.set(key.toString(), (o == object) ? r : getMSObject(o, t), t); } return r; } else { return new CString(object.toString(), t); } } /** * Given a MethodScript object, returns a java object. * * @param construct * @return */ public static Object getJavaObject(Construct construct) { if ((construct == null) || (construct instanceof CNull)) { return null; } else if (construct instanceof CVoid) { return ""; } else if (construct instanceof CBoolean) { return ((CBoolean) construct).getBoolean(); } else if (construct instanceof CInt) { return ((CInt) construct).getInt(); } else if (construct instanceof CDouble) { return ((CDouble) construct).getDouble(); } else if (construct instanceof CString) { return construct.val(); } else if (construct instanceof CByteArray) { return ((CByteArray) construct).asByteArrayCopy(); } else if (construct instanceof CResource) { return ((CResource) construct).getResource(); } else if (construct instanceof CArray) { CArray array = (CArray) construct; if (array.isAssociative()) { HashMap<String, Object> map = new HashMap<>(); for (Construct key : array.keySet()) { Construct c = array.get(key.val(), Target.UNKNOWN); map.put(key.val(), (c == array) ? map : getJavaObject(c)); } return map; } else { Object[] a = new Object[(int) array.size()]; boolean nullable = false; Class<?> clazz = null; for (int i = 0; i < array.size(); i++) { Construct c = array.get(i, Target.UNKNOWN); if (c == array) { a[i] = a; } else { a[i] = getJavaObject(array.get(i, Target.UNKNOWN)); } if (a[i] != null) { if (clazz == null) { clazz = a[i].getClass(); } else if (!clazz.equals(Object.class)) { //to test if it is possible to return something more specific than Object[] Class<?> cl = a[i].getClass(); while (!clazz.isAssignableFrom(cl)) { clazz = clazz.getSuperclass(); } } } else { nullable = true; } } if ((clazz != null) && (!clazz.equals(Object.class))) { if (clazz.equals(Boolean.class) && !nullable) { boolean[] r = new boolean[a.length]; for (int i = 0; i < a.length; i++) { r[i] = (boolean) a[i]; } return r; } if (clazz.equals(Long.class) && !nullable) { long[] r = new long[a.length]; for (int i = 0; i < a.length; i++) { r[i] = (long) a[i]; } return r; } else if (clazz.equals(Double.class) && !nullable) { double[] r = new double[a.length]; for (int i = 0; i < a.length; i++) { r[i] = (double) a[i]; } return r; } else { Object[] r = (Object[]) Array.newInstance(clazz, a.length); System.arraycopy(a, 0, r, 0, a.length); return r; } } else { return a; } } } else { return construct; } } /** * Given a locale string, returns the java locale, or null if it can't be found. * @param fromLocaleString * @return */ public static Locale GetLocale(String fromLocaleString){ for(Locale loc : Locale.getAvailableLocales()) { if(loc.toString().toLowerCase().equals(fromLocaleString.toLowerCase())) { return loc; } } return null; } }