Aliases to the same command should be considered one alias for the puzzler

This commit is contained in:
TheComputerGeek2 2017-04-21 10:46:24 -07:00 committed by Olof Larsson
parent a3f675db24
commit b7ddb71f04
2 changed files with 97 additions and 63 deletions

View File

@ -6,7 +6,6 @@ import com.massivecraft.massivecore.Lang;
import com.massivecraft.massivecore.MassiveException; import com.massivecraft.massivecore.MassiveException;
import com.massivecraft.massivecore.MassivePlugin; import com.massivecraft.massivecore.MassivePlugin;
import com.massivecraft.massivecore.collections.MassiveList; import com.massivecraft.massivecore.collections.MassiveList;
import com.massivecraft.massivecore.collections.MassiveMap;
import com.massivecraft.massivecore.collections.MassiveSet; import com.massivecraft.massivecore.collections.MassiveSet;
import com.massivecraft.massivecore.command.requirement.Requirement; import com.massivecraft.massivecore.command.requirement.Requirement;
import com.massivecraft.massivecore.command.requirement.RequirementAbstract; import com.massivecraft.massivecore.command.requirement.RequirementAbstract;
@ -15,12 +14,13 @@ import com.massivecraft.massivecore.command.type.Type;
import com.massivecraft.massivecore.command.type.enumeration.TypeEnum; import com.massivecraft.massivecore.command.type.enumeration.TypeEnum;
import com.massivecraft.massivecore.mixin.MixinMessage; import com.massivecraft.massivecore.mixin.MixinMessage;
import com.massivecraft.massivecore.mson.Mson; import com.massivecraft.massivecore.mson.Mson;
import com.massivecraft.massivecore.predicate.Predicate;
import com.massivecraft.massivecore.predicate.PredicateLevenshteinClose;
import com.massivecraft.massivecore.predicate.PredicateStartsWithIgnoreCase; import com.massivecraft.massivecore.predicate.PredicateStartsWithIgnoreCase;
import com.massivecraft.massivecore.util.MUtil; import com.massivecraft.massivecore.util.MUtil;
import com.massivecraft.massivecore.util.PermissionUtil; import com.massivecraft.massivecore.util.PermissionUtil;
import com.massivecraft.massivecore.util.ReflectionUtil; import com.massivecraft.massivecore.util.ReflectionUtil;
import com.massivecraft.massivecore.util.Txt; import com.massivecraft.massivecore.util.Txt;
import org.apache.commons.lang.StringUtils;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.command.PluginIdentifiableCommand;
@ -35,8 +35,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
public class MassiveCommand implements Active, PluginIdentifiableCommand public class MassiveCommand implements Active, PluginIdentifiableCommand
@ -392,66 +390,58 @@ public class MassiveCommand implements Active, PluginIdentifiableCommand
// -------------------------------------------- // // -------------------------------------------- //
// CHILDREN > GET // CHILDREN > GET
// -------------------------------------------- // // -------------------------------------------- //
// The full version of the child matcher method. // The full version of the child matcher method.
// Returns a map from alias to command. // Returns a set of child commands with similar aliases.
// //
// token - the full alias or an alias prefix. // token - the full alias or an alias prefix.
// levenshtein - should we use levenshtein instead of starts with? // levenshtein - should we use levenshtein instead of starts with?
// prioritizeExact - return single entry map on full match. // prioritizeExact - return single element set on full match.
// //
// An empty map means no child was found. // An empty set means no child was found.
// A single entry map means we found an unambiguous match. // A single element set means we found an unambiguous match.
// A larger map means the token was ambiguous. // A larger set means the token was ambiguous.
public Map<String, MassiveCommand> getChildMatches(String token, boolean levenshtein, CommandSender onlyRelevantToSender) private Set<MassiveCommand> getChildren(String token, boolean levenshtein, CommandSender onlyRelevantToSender)
{ {
// Create Ret // Create Ret
Map<String, MassiveCommand> ret = new MassiveMap<>(); Set<MassiveCommand> ret = new MassiveSet<>();
// Prepare // Prepare
token = token.toLowerCase(); token = token.toLowerCase();
PredicateStartsWithIgnoreCase predicate = PredicateStartsWithIgnoreCase.get(token); Predicate<String> predicate = levenshtein ? PredicateLevenshteinClose.get(token) : PredicateStartsWithIgnoreCase.get(token);
// Fill Ret // Fill Ret
// Go through each child command
for (MassiveCommand child : this.getChildren()) for (MassiveCommand child : this.getChildren())
{ {
// See if any of the aliases has a match or close enough
// If there is a direct match, return that
for (String alias : child.getAliases()) for (String alias : child.getAliases())
{ {
// If this alias has not already been reserved ...
if (ret.containsKey(alias)) continue;
// ... consider exact priority ... // ... consider exact priority ...
if (alias.equalsIgnoreCase(token)) if (alias.equalsIgnoreCase(token))
{ {
return new MassiveMap<>(alias, child); return Collections.singleton(child);
} }
if (ret.contains(child)) continue;
// ... matches ... // ... matches ...
if (levenshtein) if (!predicate.apply(alias)) continue;
{
// ... is levenshteinish ...
if ( ! this.isLevenshteinClose(token, alias)) continue;
}
else
{
// ... the alias startsWithIgnoreCase the token ...
if ( ! predicate.apply(alias)) continue;
}
// ... and put in ret. // ... and put in ret.
ret.put(alias, child); ret.add(child);
} }
} }
// Only Relevant // Only Relevant
if (onlyRelevantToSender != null) if (onlyRelevantToSender != null)
{ {
Iterator<Entry<String, MassiveCommand>> iter = ret.entrySet().iterator(); for (Iterator<MassiveCommand> iterator = ret.iterator(); iterator.hasNext(); )
while (iter.hasNext())
{ {
Entry<String, MassiveCommand> entry = iter.next(); MassiveCommand command = iterator.next();
if (entry.getValue().isRelevant(onlyRelevantToSender)) continue; if (command.isRelevant(onlyRelevantToSender)) continue;
iter.remove(); iterator.remove();
} }
} }
@ -462,41 +452,23 @@ public class MassiveCommand implements Active, PluginIdentifiableCommand
// A simplified version returning null on ambiguity and nothing found. // A simplified version returning null on ambiguity and nothing found.
public MassiveCommand getChild(String token) public MassiveCommand getChild(String token)
{ {
Map<String, MassiveCommand> childMatches = this.getChildMatches(token, false, null); Set<MassiveCommand> children = this.getChildren(token, false, null);
if (childMatches.isEmpty()) return null; if (children.isEmpty()) return null;
if (childMatches.size() > 1) return null; if (children.size() > 1) return null;
return children.iterator().next();
return childMatches.entrySet().iterator().next().getValue();
} }
protected boolean isRelevant(CommandSender sender) protected boolean isRelevant(CommandSender sender)
{ {
if (sender == null) return true; if (sender == null) return true;
if ( ! this.isVisibleTo(sender)) return false; if (!this.isVisibleTo(sender)) return false;
if ( ! this.isRequirementsMet(sender, false)) return false; if (!this.isRequirementsMet(sender, false)) return false;
return true; return true;
} }
public boolean isLevenshteinClose(String argument, String alias)
{
int levenshteinDistanceMax = this.getLevenshteinMax(argument);
int distance = StringUtils.getLevenshteinDistance(argument, alias);
return distance <= levenshteinDistanceMax;
}
public int getLevenshteinMax(String argument)
{
if (argument == null) return 0;
if (argument.length() <= 1) return 0; // When dealing with 1 character aliases, there is way too many options. So we don't suggest.
if (argument.length() <= 4) return 1; // When dealing with low length aliases, there too many options. So we won't suggest much
if (argument.length() <= 7) return 2; // 2 is default.
return 3; // If it were 8 characters or more, we end up here. Because many characters allow for more typos.
}
// -------------------------------------------- // // -------------------------------------------- //
// ALIASES // ALIASES
// -------------------------------------------- // // -------------------------------------------- //
@ -1103,19 +1075,20 @@ public class MassiveCommand implements Active, PluginIdentifiableCommand
this.setArgs(args); this.setArgs(args);
// Requirements // Requirements
if ( ! this.isRequirementsMet(sender, true)) return; if (!this.isRequirementsMet(sender, true)) return;
// Child Execution // Child Execution
if (this.isParent() && args.size() > 0) if (this.isParent() && args.size() > 0)
{ {
// Get matches // Get matches
String token = args.get(0); String token = args.get(0);
Map<String, MassiveCommand> matches = this.getChildMatches(token, false, null);
Set<MassiveCommand> matches = this.getChildren(token, false, null);
// Score! // Score!
if (matches.size() == 1) if (matches.size() == 1)
{ {
MassiveCommand child = matches.entrySet().iterator().next().getValue(); MassiveCommand child = matches.iterator().next();
args.remove(0); args.remove(0);
child.execute(sender, args); child.execute(sender, args);
} }
@ -1128,12 +1101,12 @@ public class MassiveCommand implements Active, PluginIdentifiableCommand
if (matches.isEmpty()) if (matches.isEmpty())
{ {
base = Lang.COMMAND_CHILD_NONE; base = Lang.COMMAND_CHILD_NONE;
suggestions = this.getChildMatches(token, true, sender).values(); suggestions = this.getChildren(token, true, sender);
} }
else else
{ {
base = Lang.COMMAND_CHILD_AMBIGUOUS; base = Lang.COMMAND_CHILD_AMBIGUOUS;
suggestions = this.getChildMatches(token, false, sender).values(); suggestions = this.getChildren(token, false, sender);
} }
// Message: "The sub command X couldn't be found." // Message: "The sub command X couldn't be found."
@ -1158,7 +1131,7 @@ public class MassiveCommand implements Active, PluginIdentifiableCommand
} }
// Self Execution > Arguments Valid // Self Execution > Arguments Valid
if ( ! this.isArgsValid(this.getArgs(), this.sender)) return; if (!this.isArgsValid(this.getArgs(), this.sender)) return;
// Self Execution > Perform // Self Execution > Perform
this.perform(); this.perform();

View File

@ -0,0 +1,61 @@
package com.massivecraft.massivecore.predicate;
import org.apache.commons.lang.StringUtils;
public class PredicateLevenshteinClose implements Predicate<String>
{
// -------------------------------------------- //
// FIELDS
// -------------------------------------------- //
private String token;
private int levenshteinMax;
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public static PredicateLevenshteinClose get(String token) { return new PredicateLevenshteinClose(token); }
public static PredicateLevenshteinClose get(String token, int levenshteinMax) { return new PredicateLevenshteinClose(token, levenshteinMax); }
public PredicateLevenshteinClose(String token)
{
this(token, getLevenshteinMax(token));
}
public PredicateLevenshteinClose(String token, int levenshteinMax)
{
if (token == null) throw new NullPointerException("token");
this.token = token;
this.levenshteinMax = levenshteinMax;
}
// -------------------------------------------- //
// OVERRIDE
// -------------------------------------------- //
@Override
public boolean apply(String type)
{
if (type == null) return false;
int distance = StringUtils.getLevenshteinDistance(this.token, type);
return distance <= this.levenshteinMax;
}
// -------------------------------------------- //
// UTIL
// -------------------------------------------- //
// This is the standard distance we tolerate for command aliases
public static int getLevenshteinMax(String argument)
{
if (argument == null) return 0;
if (argument.length() <= 1) return 0; // When dealing with 1 character aliases, there is way too many options. So we don't suggest.
if (argument.length() <= 4) return 1; // When dealing with low length aliases, there too many options. So we won't suggest much
if (argument.length() <= 7) return 2; // 2 is default.
return 3; // If it were 8 characters or more, we end up here. Because many characters allow for more typos.
}
}