/* * Copyright (c) 2006-2011 DMDirc Developers * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.dmdirc.commandparser.commands.flags; import com.dmdirc.FrameContainer; import com.dmdirc.commandparser.CommandArguments; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Utility class for commands which allow the user to specify shell-like * flags (--foo). * * @since 0.6.5 */ public class CommandFlagHandler { /** A map of all known flag names to their flag objects. */ private final Map flags = new HashMap(); /** A map of currently enabled flag names to their flag objects. */ private final Map enabledFlags = new HashMap(); /** A map of currently disabled flag names to their flag objects. */ private final Map disabledFlags = new HashMap(); /** A map of disabled flag names to the flag objects that caused them to be disabled. */ private final Map disabledBy = new HashMap(); /** * Creates a new command flag handler which will handle all of the specified * flags. * * @param flags The flags that will be handled */ public CommandFlagHandler(final CommandFlag ... flags) { for (CommandFlag flag : flags) { this.flags.put(flag.getName(), flag); } } /** * Processes the specified arguments and parses out command flags. If * the specified arguments aren't valid given the flags belonging to * this command flag handler, an error message is sent to the origin and * null is returned from this method. * * @param origin The container where the command was entered * @param arguments The arguments passed to the command * @return A corresponding {@link CommandFlagResult} object, or null * if some problem was encountered. */ public CommandFlagResult process(final FrameContainer origin, final CommandArguments arguments) { final Map results = parse(origin, arguments); return results == null ? null : new CommandFlagResult(arguments, results); } /** * Parses the specified arguments and returns the offsets of the arguments * for each found command flag. * * @param origin The container where the command was entered * @param arguments The arguments passed to the command * @return A map of discovered command flags to the offset of the flag's * first argument within the arguments object. If an error * occurs, null is returned. */ protected Map parse(final FrameContainer origin, final CommandArguments arguments) { enabledFlags.clear(); disabledBy.clear(); disabledFlags.clear(); for (CommandFlag flag : flags.values()) { (flag.isEnabled() ? enabledFlags : disabledFlags).put(flag.getName(), flag); } final Map results = new HashMap(); final List delayedFlags = new ArrayList(flags.size()); int offset; for (offset = 0; offset < arguments.getArguments().length; offset++) { final String arg = arguments.getArguments()[offset]; String name; if (arg.startsWith("--") && flags.containsKey(name = arg.substring(2).toLowerCase())) { final CommandFlag flag = flags.get(name); if (enabledFlags.containsKey(name)) { // It's enabled! handleEnable(flag); // Handle any immediate arguments if ((offset = readArguments(flag, arguments, offset + 1, flag.getImmediateArgs(), origin, results)) == -1) { return null; } // Handle delayed arguments (if any) if (flag.getDelayedArgs() > 0) { delayedFlags.add(flag); } } else if (disabledBy.containsKey(name)) { // Disabled by another flag sendLine(origin, arguments.isSilent(), "commandError", "Cannot use flag --" + name + " in conjunction with --" + disabledBy.get(name).getName()); return null; } else { // Disabled because not yet enabled sendLine(origin, arguments.isSilent(), "commandError", "Cannot use flag --" + name + " without " + getEnablers(flag)); return null; } } else { break; } } // Handle any stored delayed arguments for (CommandFlag flag : delayedFlags) { if ((offset = readArguments(flag, arguments, offset, flag.getDelayedArgs(), origin, results)) == -1) { return null; } offset++; } results.put(null, offset); return results; } /** * Reads the arguments for the specified flag. * * @param flag The flag that is being read * @param arguments The raw arguments for the command * @param offset The index that the first argument will be at * @param argCount The number of arguments that need to be read * @param origin The source of the command (for error messages) * @param results The map to place results into * @return The index of the last argument that was handled, or -1 if * there were insufficient arguments for the flag */ protected int readArguments(final CommandFlag flag, final CommandArguments arguments, final int offset, final int argCount, final FrameContainer origin, final Map results) { final int lastArg = argCount + offset - 1; if (arguments.getArguments().length <= lastArg) { sendLine(origin, arguments.isSilent(), "commandError", "Flag --" + flag.getName() + " expects " + argCount + " argument" + (argCount == 1 ? "" : "s")); return -1; } results.put(flag, offset); return lastArg; } /** * Processes the enabled and disabled lists for the specified flag, and * adds them to the relevant properties. * * @param flag The flag whose enables/disables lists should be processed */ protected void handleEnable(final CommandFlag flag) { for (CommandFlag target : flag.getDisables()) { if (enabledFlags.containsKey(target.getName())) { enabledFlags.remove(target.getName()); disabledFlags.put(target.getName(), target); disabledBy.put(target.getName(), flag); } } for (CommandFlag target : flag.getEnables()) { if (disabledFlags.containsKey(target.getName())) { disabledFlags.remove(target.getName()); enabledFlags.put(target.getName(), target); disabledBy.remove(target.getName()); } } } /** * Constructs a user-friendly string describing the flag(s) which must * be used in order to enable the specified flag. This is useful for * error messages when the user tries to use a disabled flag. * * @param flag The flag to find enablers for * @return A user-friendly string describing flags which enable the * specified flag. */ protected String getEnablers(final CommandFlag flag) { final List enablers = new LinkedList(); for (CommandFlag target : flags.values()) { if (target.getEnables().contains(flag)) { enablers.add(target); } } if (enablers.size() == 1) { return "--" + enablers.get(0).getName(); } final StringBuilder res = new StringBuilder("one of "); for (CommandFlag enabler : enablers) { res.append("--"); res.append(enabler.getName()); res.append(", "); } return res.substring(0, res.length() - 2); } /** * Convenience method to send a line to the specified frame container. * * @param origin The container to send the line to * @param isSilent Whether the command is silenced or not * @param messageType The type of the line to be sent * @param args The arguments for the specified messageType */ protected static void sendLine(final FrameContainer origin, final boolean isSilent, final String messageType, final Object ... args) { if (origin != null && !isSilent) { origin.addLine(messageType, args); } } }