package ring.commands.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import ring.commands.WorldObjectSearch;
import ring.commands.annotations.Scope;
import ring.mobiles.Mobile;
import ring.movement.Room;
import ring.world.WorldObject;
public class ParsedCommand {
private String formID;
private String command;
private List<Object> arguments = new ArrayList<Object>();
private Scope cascadeType;
private Scope scope;
public String getFormID() {
return formID;
}
public void setFormID(String id) {
formID = id;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public List<Object> getArguments() {
return arguments;
}
public void setArguments(List<Object> args) {
arguments = args;
}
public void setArguments(Object ... args) {
arguments.addAll(Arrays.asList(args));
}
public void addArgument(Object arg) {
arguments.add(arg);
}
public Object getArgument(int index) {
return arguments.get(index);
}
public void setCascadeType(Scope cascadeType) {
this.cascadeType = cascadeType;
}
public Scope getCascadeType() {
return cascadeType;
}
public void setScope(Scope scope) {
this.scope = scope;
}
public Scope getScope() {
return scope;
}
protected void initialize(CommandSender sender, List<ParsedCommandToken> tokens) {
if (tokens == null || tokens.size() == 0) {
setArguments(sender.getContext().getLocation());
return;
}
else {
if (this.getCascadeType() == Scope.LTR_CASCADING) {
initializeLTRCascade(sender, tokens);
}
else if (this.getCascadeType() == Scope.RTL_CASCADING) {
initializeRTLCascade(sender, tokens);
}
else if (this.getCascadeType() == Scope.NO_CASCADING) {
initializeNoCascade(sender, tokens);
}
}
}
private void initializeNoCascade(CommandSender sender, List<ParsedCommandToken> tokens) {
for (ParsedCommandToken token : tokens) {
addArgument(token.getToken());
}
}
/**
* Performs object translation for a command that cascades its data left-to-right.
* @param sender
* @param tokens
*/
private void initializeLTRCascade(CommandSender sender, List<ParsedCommandToken> tokens) {
List<Object> arguments = new ArrayList<Object>(tokens.size());
ParsedCommandToken first = tokens.get(0);
Scope scope = first.getMatched().getScope();
//Retrieve initial WO dataset from first token based on its scope and bind types.
//Filter that list via WorldObjectSearch and bind the first result to the argument
//Begin looping over rest of variables:
// Use previous WO's produceSearchList(Class ... cs) method based on bind types for the current PT
// Filter WO list based on parsed token and set the argument.
WorldObject rootArg = null;
if (scope == Scope.ROOM) {
Room location = sender.getContext().getLocation();
rootArg = worldObjectFromToken(first, location);
}
else if (scope == Scope.MOBILE) {
//Room location = sender.getContext().getLocation();
//rootArg = worldObjectForMobScope(first, location);
throw new IllegalArgumentException("Mobile scope is unsupported at this time.");
}
else if (scope == Scope.SELF) {
throw new IllegalArgumentException("Self scope is unsupported at this time.");
}
if (rootArg == null) {
nullArgs(arguments, tokens.size());
setArguments(arguments);
return;
}
arguments.add(rootArg);
WorldObject previousArg = rootArg;
for (int c = 1; c < tokens.size(); c++) {
//Error detection: object not found.
if (previousArg == null) {
nullArgs(arguments, tokens.size() - c);
setArguments(arguments);
return;
}
//Only search for world objects if the type is not preserved text.
ParsedCommandToken token = tokens.get(c);
if (!token.getMatched().isText()) {
WorldObject arg = worldObjectFromToken(token, previousArg);
arguments.add(arg);
previousArg = arg;
}
else {
arguments.add(token.getToken());
}
}
setArguments(arguments);
}
/**
* Performs object translation for a command that cascades its data right-to-left.
* @param sender
* @param tokens
*/
private void initializeRTLCascade(CommandSender sender, List<ParsedCommandToken> tokens) {
List<Object> arguments = new ArrayList<Object>(tokens.size());
ParsedCommandToken last = tokens.get(tokens.size() - 1);
Scope scope = last.getMatched().getScope();
//Retrieve initial WO dataset from first token based on its scope and bind types.
//Filter that list via WorldObjectSearch and bind the first result to the argument
//Begin looping over rest of variables:
// Use previous WO's produceSearchList(Class ... cs) method based on bind types for the current PT
// Filter WO list based on parsed token and set the argument.
WorldObject rootArg = null;
if (scope == Scope.ROOM) {
Room location = sender.getContext().getLocation();
rootArg = worldObjectFromToken(last, location);
}
else if (scope == Scope.MOBILE) {
//Room location = sender.getContext().getLocation();
//rootArg = worldObjectForMobScope(first, location);
throw new IllegalArgumentException("Mobile scope is unsupported at this time.");
}
else if (scope == Scope.SELF) {
throw new IllegalArgumentException("Self scope is unsupported at this time.");
}
if (rootArg == null) {
nullArgs(arguments, tokens.size());
Collections.reverse(arguments);
setArguments(arguments);
return;
}
arguments.add(rootArg);
WorldObject previousArg = rootArg;
for (int c = tokens.size() - 2; c >= 0; c--) {
//Error detection: object not found.
if (previousArg == null) {
nullArgs(arguments, tokens.size() - (tokens.size() - c) + 1);
Collections.reverse(arguments);
setArguments(arguments);
return;
}
//Only search for world object if the matched token is not preserved text.
ParsedCommandToken token = tokens.get(c);
if (!token.getMatched().isText()) {
WorldObject arg = worldObjectFromToken(token, previousArg);
arguments.add(arg);
previousArg = arg;
}
else {
arguments.add(token.getToken());
}
}
//Must be reversed since arguments are added to the list while going backwards
//through parsed command tokens
Collections.reverse(arguments);
setArguments(arguments);
}
private void nullArgs(List<Object> arguments, int length) {
for (int c = 0; c < length; c++) {
arguments.add(null);
}
}
/**
* Finds a world object based on the given command token. Uses the specified WorldObject as a
* data source to find WorldObjects to search for.
* @param token
* @param datasource
* @return
*/
@SuppressWarnings("unchecked")
private WorldObject worldObjectFromToken(ParsedCommandToken token, WorldObject datasource) {
List<Class<?>> bindTypes = token.getMatched().getBindTypes();
List<WorldObject> objs = datasource.produceSearchList(bindTypes);
return search(token.getToken(), objs);
}
/**
* Finds a world object based on the given command token. Uses the specified Room as a
* data source to find the WorldObjects to search for.
* @param token
* @param datasource
* @return
*/
@SuppressWarnings("unchecked")
private WorldObject worldObjectFromToken(ParsedCommandToken token, Room datasource) {
List<Class<?>> bindTypes = token.getMatched().getBindTypes();
List<WorldObject> objs = datasource.produceSearchList(bindTypes);
return search(token.getToken(), objs);
}
/**
* Unused currently. Will theoretically be for commands that operate within a "shell"
* that knows about a target Mobile.
* @param token
* @param datasource
* @return
*/
@SuppressWarnings({ "unchecked", "unused" })
private WorldObject worldObjectForMobScope(ParsedCommandToken token, Room datasource) {
Class<?>[] bindTypes = new Class<?>[] { Mobile.class };
List<WorldObject> objs = datasource.produceSearchList(bindTypes);
return search(token.getToken(), objs);
}
/**
* This method delegates to {@link ring.commands.WorldObjectSearch} in order
* to search collections of world objects from generic data sources. It returns
* the most relevant world object found amongst all presented collections. The
* text searched for is case-insensitive.
* @param name The name to search for.
* @param worldObjectLists {@link java.util.Collection}s of {@link WorldObject}s.
* @return The most relevant world object, or null if nothing was found.
*/
private WorldObject search(String name, Collection<? extends WorldObject> ... worldObjectLists) {
WorldObjectSearch search = new WorldObjectSearch();
for (Collection<? extends WorldObject> list : worldObjectLists) {
search.addSearchList(list);
}
List<WorldObject> results = search.search(name);
if (results.size() > 0) {
return results.get(0);
}
else {
return null;
}
}
}