You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommandFlagHandler.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /*
  2. * Copyright (c) 2006-2012 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.commandparser.commands.flags;
  23. import com.dmdirc.FrameContainer;
  24. import com.dmdirc.commandparser.CommandArguments;
  25. import java.util.ArrayList;
  26. import java.util.HashMap;
  27. import java.util.LinkedList;
  28. import java.util.List;
  29. import java.util.Map;
  30. /**
  31. * Utility class for commands which allow the user to specify shell-like
  32. * flags (<code>--foo</code>).
  33. *
  34. * @since 0.6.5
  35. */
  36. public class CommandFlagHandler {
  37. /** A map of all known flag names to their flag objects. */
  38. private final Map<String, CommandFlag> flags = new HashMap<String, CommandFlag>();
  39. /** A map of currently enabled flag names to their flag objects. */
  40. private final Map<String, CommandFlag> enabledFlags = new HashMap<String, CommandFlag>();
  41. /** A map of currently disabled flag names to their flag objects. */
  42. private final Map<String, CommandFlag> disabledFlags = new HashMap<String, CommandFlag>();
  43. /** A map of disabled flag names to the flag objects that caused them to be disabled. */
  44. private final Map<String, CommandFlag> disabledBy = new HashMap<String, CommandFlag>();
  45. /**
  46. * Creates a new command flag handler which will handle all of the specified
  47. * flags.
  48. *
  49. * @param flags The flags that will be handled
  50. */
  51. public CommandFlagHandler(final CommandFlag ... flags) {
  52. for (CommandFlag flag : flags) {
  53. this.flags.put(flag.getName(), flag);
  54. }
  55. }
  56. /**
  57. * Processes the specified arguments and parses out command flags. If
  58. * the specified arguments aren't valid given the flags belonging to
  59. * this command flag handler, an error message is sent to the origin and
  60. * <code>null</code> is returned from this method.
  61. *
  62. * @param origin The container where the command was entered
  63. * @param arguments The arguments passed to the command
  64. * @return A corresponding {@link CommandFlagResult} object, or null
  65. * if some problem was encountered.
  66. */
  67. public CommandFlagResult process(final FrameContainer origin,
  68. final CommandArguments arguments) {
  69. final Map<CommandFlag, Integer> results = parse(origin, arguments);
  70. return results == null ? null : new CommandFlagResult(arguments, results);
  71. }
  72. /**
  73. * Parses the specified arguments and returns the offsets of the arguments
  74. * for each found command flag.
  75. *
  76. * @param origin The container where the command was entered
  77. * @param arguments The arguments passed to the command
  78. * @return A map of discovered command flags to the offset of the flag's
  79. * first argument within the <code>arguments</code> object. If an error
  80. * occurs, null is returned.
  81. */
  82. protected Map<CommandFlag, Integer> parse(final FrameContainer origin,
  83. final CommandArguments arguments) {
  84. enabledFlags.clear();
  85. disabledBy.clear();
  86. disabledFlags.clear();
  87. for (CommandFlag flag : flags.values()) {
  88. (flag.isEnabled() ? enabledFlags : disabledFlags).put(flag.getName(), flag);
  89. }
  90. final Map<CommandFlag, Integer> results = new HashMap<CommandFlag, Integer>();
  91. final List<CommandFlag> delayedFlags = new ArrayList<CommandFlag>(flags.size());
  92. int offset;
  93. for (offset = 0; offset < arguments.getArguments().length; offset++) {
  94. final String arg = arguments.getArguments()[offset];
  95. String name;
  96. if (arg.startsWith("--")
  97. && flags.containsKey(name = arg.substring(2).toLowerCase())) {
  98. final CommandFlag flag = flags.get(name);
  99. if (enabledFlags.containsKey(name)) {
  100. // It's enabled!
  101. handleEnable(flag);
  102. // Handle any immediate arguments
  103. if ((offset = readArguments(flag, arguments, offset + 1,
  104. flag.getImmediateArgs(), origin, results)) == -1) {
  105. return null;
  106. }
  107. // Handle delayed arguments (if any)
  108. if (flag.getDelayedArgs() > 0) {
  109. delayedFlags.add(flag);
  110. }
  111. } else if (disabledBy.containsKey(name)) {
  112. // Disabled by another flag
  113. sendLine(origin, arguments.isSilent(), "commandError",
  114. "Cannot use flag --" + name
  115. + " in conjunction with --" + disabledBy.get(name).getName());
  116. return null;
  117. } else {
  118. // Disabled because not yet enabled
  119. sendLine(origin, arguments.isSilent(), "commandError",
  120. "Cannot use flag --" + name
  121. + " without " + getEnablers(flag));
  122. return null;
  123. }
  124. } else {
  125. break;
  126. }
  127. }
  128. // Handle any stored delayed arguments
  129. for (CommandFlag flag : delayedFlags) {
  130. if ((offset = readArguments(flag, arguments, offset,
  131. flag.getDelayedArgs(), origin, results)) == -1) {
  132. return null;
  133. }
  134. offset++;
  135. }
  136. results.put(null, offset);
  137. return results;
  138. }
  139. /**
  140. * Reads the arguments for the specified flag.
  141. *
  142. * @param flag The flag that is being read
  143. * @param arguments The raw arguments for the command
  144. * @param offset The index that the first argument will be at
  145. * @param argCount The number of arguments that need to be read
  146. * @param origin The source of the command (for error messages)
  147. * @param results The map to place results into
  148. * @return The index of the last argument that was handled, or -1 if
  149. * there were insufficient arguments for the flag
  150. */
  151. protected int readArguments(final CommandFlag flag,
  152. final CommandArguments arguments, final int offset, final int argCount,
  153. final FrameContainer origin, final Map<CommandFlag, Integer> results) {
  154. final int lastArg = argCount + offset - 1;
  155. if (arguments.getArguments().length <= lastArg) {
  156. sendLine(origin, arguments.isSilent(),
  157. "commandError", "Flag --" + flag.getName() + " expects "
  158. + argCount + " argument"
  159. + (argCount == 1 ? "" : "s"));
  160. return -1;
  161. }
  162. results.put(flag, offset);
  163. return lastArg;
  164. }
  165. /**
  166. * Processes the enabled and disabled lists for the specified flag, and
  167. * adds them to the relevant properties.
  168. *
  169. * @param flag The flag whose enables/disables lists should be processed
  170. */
  171. protected void handleEnable(final CommandFlag flag) {
  172. for (CommandFlag target : flag.getDisables()) {
  173. if (enabledFlags.containsKey(target.getName())) {
  174. enabledFlags.remove(target.getName());
  175. disabledFlags.put(target.getName(), target);
  176. disabledBy.put(target.getName(), flag);
  177. }
  178. }
  179. for (CommandFlag target : flag.getEnables()) {
  180. if (disabledFlags.containsKey(target.getName())) {
  181. disabledFlags.remove(target.getName());
  182. enabledFlags.put(target.getName(), target);
  183. disabledBy.remove(target.getName());
  184. }
  185. }
  186. }
  187. /**
  188. * Constructs a user-friendly string describing the flag(s) which must
  189. * be used in order to enable the specified flag. This is useful for
  190. * error messages when the user tries to use a disabled flag.
  191. *
  192. * @param flag The flag to find enablers for
  193. * @return A user-friendly string describing flags which enable the
  194. * specified flag.
  195. */
  196. protected String getEnablers(final CommandFlag flag) {
  197. final List<CommandFlag> enablers = new LinkedList<CommandFlag>();
  198. for (CommandFlag target : flags.values()) {
  199. if (target.getEnables().contains(flag)) {
  200. enablers.add(target);
  201. }
  202. }
  203. if (enablers.size() == 1) {
  204. return "--" + enablers.get(0).getName();
  205. }
  206. final StringBuilder res = new StringBuilder("one of ");
  207. for (CommandFlag enabler : enablers) {
  208. res.append("--");
  209. res.append(enabler.getName());
  210. res.append(", ");
  211. }
  212. return res.substring(0, res.length() - 2);
  213. }
  214. /**
  215. * Convenience method to send a line to the specified frame container.
  216. *
  217. * @param origin The container to send the line to
  218. * @param isSilent Whether the command is silenced or not
  219. * @param messageType The type of the line to be sent
  220. * @param args The arguments for the specified messageType
  221. */
  222. protected static void sendLine(final FrameContainer origin, final boolean isSilent,
  223. final String messageType, final Object ... args) {
  224. if (origin != null && !isSilent) {
  225. origin.addLine(messageType, args);
  226. }
  227. }
  228. }