package me.desht.scrollingmenusign.variables;
import com.google.common.collect.Lists;
import me.desht.dhutils.*;
import me.desht.scrollingmenusign.*;
import me.desht.scrollingmenusign.util.Substitutions;
import me.desht.scrollingmenusign.views.action.*;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
public class VariablesManager implements Observer {
public static final UUID GLOBAL_UUID = new UUID(0, 0);
private static final String DEFAULT_MARKER = "*";
private final Map<String, YamlConfiguration> toMigrate = new HashMap<String, YamlConfiguration>();
private final Map<UUID, SMSVariables> allVariables = new HashMap<UUID, SMSVariables>();
private final Map<String, Set<String>> menuUsage = new HashMap<String, Set<String>>();
private final ScrollingMenuSign plugin;
public VariablesManager(ScrollingMenuSign plugin) {
this.plugin = plugin;
}
public void clear() {
allVariables.clear();
}
public void load(File f) {
YamlConfiguration conf = YamlConfiguration.loadConfiguration(f);
String playerName = f.getName().replaceAll("\\.yml$", "");
if (MiscUtil.looksLikeUUID(playerName)) {
SMSVariables vars = getVariables(UUID.fromString(playerName), true);
for (String key : conf.getKeys(false)) {
vars.set(key, conf.getString(key));
}
} else {
toMigrate.put(playerName, conf);
}
}
public void checkForUUIDMigration() {
if (toMigrate.isEmpty()) {
return;
}
LogUtils.info("Migrating user variables for " + toMigrate.size() + " user(s)");
final UUIDFetcher uf = new UUIDFetcher(new ArrayList<String>(toMigrate.keySet()), true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
try {
new SyncUUIDTask(uf.call()).runTask(plugin);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Get the variable collection for the given player. If the player has no variables,
* a new empty SMSVariables collection will be created iff autoCreate is true, otherwise
* an exception will be thrown.
*
* @param sender the command sender
* @param autoCreate if true, a variables object will be auto-created if it doesn't exist
* @return an SMSVariables collection of variables for the player
* @throws SMSException if autoCreate is false and the variables object does not exist
*/
public SMSVariables getVariables(CommandSender sender, boolean autoCreate) {
UUID id = sender instanceof ConsoleCommandSender ? GLOBAL_UUID : ((Player) sender).getUniqueId();
return getVariables(id, autoCreate);
}
/**
* Get the variable collection for the given player. If the player has no variables,
* a new empty SMSVariables collection will be created iff autoCreate is true, otherwise
* an exception will be thrown.
*
* @param playerId the player's UUID
* @param autoCreate if true, a variables object will be auto-created if it doesn't exist
* @return an SMSVariables collection of variables for the player
* @throws SMSException if autoCreate is false and the variables object does not exist
*/
public SMSVariables getVariables(UUID playerId, boolean autoCreate) {
if (!hasVariables(playerId)) {
SMSValidate.isTrue(autoCreate, "No variables are defined for player ID " + playerId);
allVariables.put(playerId, new SMSVariables(playerId));
}
return allVariables.get(playerId);
}
/**
* Check if any variables are defined for the given player.
*
* @param playerId the name of the player to check for
* @return true if variables are defined for the player, false otherwise
*/
public boolean hasVariables(UUID playerId) {
return allVariables.containsKey(playerId);
}
/**
* Get a list of all the known SMSVariables collections
*
* @return a list of all the known SMSVariables collections
*/
public Collection<SMSVariables> listVariables() {
return listVariables(false);
}
/**
* Get a (possibly sorted) list of all the known SMSVariables collections
*
* @param isSorted true if the result should be sorted by variable name
* @return a list of all the known SMSVariables collections
*/
private Collection<SMSVariables> listVariables(boolean isSorted) {
if (isSorted) {
SortedSet<UUID> sorted = new TreeSet<UUID>(allVariables.keySet());
List<SMSVariables> res = new ArrayList<SMSVariables>();
for (UUID name : sorted) {
res.add(allVariables.get(name));
}
return res;
} else {
return new ArrayList<SMSVariables>(allVariables.values());
}
}
/**
* Get the value of the given variable spec. The spec may be a simple variable name or
* a player UUID, or 'console', followed by a period, followed by the variable name.
*
* @param sender the command sender who is retrieving the variable
* @param varSpec the variable specification
* @return the variable value, or null if not set
* @throws SMSException is varSpec is not a well-formed variable spec
*/
public String get(CommandSender sender, String varSpec) {
return get(sender, varSpec, null);
}
/**
* Get the value of the given variable spec. The spec may be a simple variable name or
* a player UUID, or 'console', followed by a period, followed by the variable name.
*
* @param sender the command sender who is retrieving the variable
* @param varSpec the variable specification
* @param defValue default value to use if the variable is not set
* @return the variable value, or the default value if not set
* @throws SMSException is varSpec is not a well-formed variable spec
*/
public String get(CommandSender sender, String varSpec, String defValue) {
VarSpec vs = new VarSpec(sender, varSpec);
if (hasVariables(vs.getPlayerId()) && getVariables(vs.getPlayerId(), false).isSet(vs.getVarName())) {
return getVariables(vs.getPlayerId(), false).get(vs.getVarName());
} else {
if (hasVariables(GLOBAL_UUID)) {
return getVariables(GLOBAL_UUID, false).get(vs.getVarName(), defValue);
} else {
return defValue;
}
}
}
/**
* Set the given variable spec. to the given value.
*
* @param sender the command sender who is retrieving the variable
* @param varSpec the variable specification
* @param value new value for the variable
* @throws SMSException is varSpec is not a well-formed variable spec
*/
public void set(CommandSender sender, String varSpec, String value) {
VarSpec vs = new VarSpec(sender, varSpec);
if (sender instanceof Player && vs.getPlayerId() != GLOBAL_UUID && !((Player) sender).getUniqueId().equals(vs.getPlayerId())) {
PermissionUtils.requirePerms(sender, "scrollingmenusign.vars.other");
}
getVariables(vs.getPlayerId(), value != null).set(vs.getVarName(), value);
for (SMSMenu menu : getMenusUsingVariable(vs.getVarName())) {
menu.forceUpdate(new RepaintAction(sender));
}
}
/**
* Check if the given variable spec exists. The spec may be a simple variable name or
* a player UUID, or 'console', followed by a period, followed by the variable name.
*
* @param sender the command sender to check
* @param varSpec the variable specification
* @return true if the variable exists, false otherwise
* @throws SMSException is varSpec is not a well-formed variable spec
*/
public boolean isSet(CommandSender sender, String varSpec) {
VarSpec vs = new VarSpec(sender, varSpec);
return hasVariables(vs.getPlayerId()) && getVariables(vs.getPlayerId(), false).isSet(vs.getVarName());
}
/**
* Given a string, attempt to extract a UUID from it. The string must be either
* a valid UUID string, or be equals to "console" or start with a '*', in which case
* the special global UUID will be returned.
*
* @param s the string to extract
* @return a UUID from the string
* @throws SMSException if the string can't be extracted
*/
public UUID getIDFromString(String s) {
if (MiscUtil.looksLikeUUID(s)) {
return UUID.fromString(s);
} else if (s.startsWith(DEFAULT_MARKER) || s.equalsIgnoreCase("console")) {
return GLOBAL_UUID;
} else {
throw new SMSException("Player ID should be '*', 'console' or a valid UUID");
}
}
public List<SMSMenu> getMenusUsingVariable(String varName) {
Set<String> menuNames = menuUsage.get(varName);
if (menuNames == null) {
return Collections.emptyList();
} else {
SMSHandler h = ScrollingMenuSign.getInstance().getHandler();
List<SMSMenu> res = Lists.newArrayList();
for (String menuName : menuNames) {
if (h.checkMenu(menuName)) {
res.add(h.getMenu(menuName));
}
}
return res;
}
}
public void updateVariableUsage(SMSMenu menu) {
for (Set<String> s : menuUsage.values()) {
s.remove(menu.getName());
}
updateVarRefs(menu.getTitle(), menu, true);
for (SMSMenuItem item : menu.getItems()) {
updateVariableUsage(item, true);
}
}
public void updateVariableUsage(SMSMenuItem item, boolean adding) {
updateVarRefs(item.getLabel(), item.getMenu(), adding);
for (String l : item.getLore()) {
updateVarRefs(l, item.getMenu(), adding);
}
}
private void updateVarRefs(String str, SMSMenu menu, boolean adding) {
Matcher m = Substitutions.userVarSubPat.matcher(str);
while (m.find()) {
VarSpec vs = new VarSpec(null, m.group(1));
if (!menuUsage.containsKey(vs.getVarName())) {
menuUsage.put(vs.getVarName(), new HashSet<String>());
}
if (adding) {
menuUsage.get(vs.getVarName()).add(menu.getName());
Debugger.getInstance().debug("variable [" + vs.getVarName() + "] linked to menu: " + menu.getName());
} else {
menuUsage.get(vs.getVarName()).remove(menu.getName());
Debugger.getInstance().debug("variable [" + vs.getVarName() + "] unlinked from menu: " + menu.getName());
}
}
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof SMSMenu) {
if (arg instanceof AddItemAction) {
updateVariableUsage(((AddItemAction) arg).getModifiedItem(), true);
} else if (arg instanceof RemoveItemAction) {
updateVariableUsage(((RemoveItemAction) arg).getModifiedItem(), false);
} else if (arg instanceof UpdateItemAction) {
updateVariableUsage(((UpdateItemAction) arg).getModifiedItem(), false);
updateVariableUsage(((UpdateItemAction) arg).getNewItem(), true);
} else if (arg instanceof TitleAction) {
SMSMenu menu = (SMSMenu) o;
updateVarRefs(((TitleAction) arg).getOldTitle(), menu, false);
updateVarRefs(((TitleAction) arg).getNewTitle(), menu, true);
}
}
}
private class VarSpec {
private final UUID playerId;
private final String varName;
private VarSpec(CommandSender sender, String spec) {
String[] parts = spec.split("\\.", 2);
if (parts.length == 1) {
// unqualified variable - <var>
playerId = sender instanceof Player ? ((Player) sender).getUniqueId() : GLOBAL_UUID;
varName = parts[0];
} else {
// qualified variable - <player>.<var>
playerId = getIDFromString(parts[0]);
varName = parts[1];
}
SMSValidate.isTrue(varName.matches("[a-zA-Z0-9_]+"), "Invalid variable name: " + spec + " (must be all alphanumeric)");
}
private UUID getPlayerId() {
return playerId;
}
private String getVarName() {
return varName;
}
}
private class SyncUUIDTask extends BukkitRunnable {
private final Map<String, UUID> map;
public SyncUUIDTask(Map<String, UUID> map) {
this.map = map;
}
@Override
public void run() {
for (String playerName : map.keySet()) {
UUID id = map.get(playerName);
if (id != null) {
YamlConfiguration conf = toMigrate.get(playerName);
SMSVariables vars = getVariables(id, true);
for (String key : conf.getKeys(false)) {
vars.set(key, conf.getString(key));
}
vars.autosave();
File f = new File(DirectoryStructure.getVarsFolder(), playerName + ".yml");
if (!f.delete()) {
LogUtils.warning("failed to delete old variables file: " + f);
}
} else {
LogUtils.warning("can't find UUID for player: " + playerName);
}
}
toMigrate.clear();
LogUtils.info("User variables migration complete");
}
}
}