org.apache.commons.cli2.option.GroupImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.cli2.option.GroupImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.cli2.option;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.cli2.Argument;
import org.apache.commons.cli2.DisplaySetting;
import org.apache.commons.cli2.Group;
import org.apache.commons.cli2.HelpLine;
import org.apache.commons.cli2.Option;
import org.apache.commons.cli2.OptionException;
import org.apache.commons.cli2.WriteableCommandLine;
import org.apache.commons.cli2.resource.ResourceConstants;

/**
 * An implementation of Group
 */
public class GroupImpl extends OptionImpl implements Group {
    private final String name;
    private final String description;
    private final List options;
    private final int minimum;
    private final int maximum;
    private final List anonymous;
    private final SortedMap optionMap;
    private final Set prefixes;

    /**
     * Creates a new GroupImpl using the specified parameters.
     *
     * @param options the Options and Arguments that make up the Group
     * @param name the name of this Group, or null
     * @param description a description of this Group
     * @param minimum the minimum number of Options for a valid CommandLine
     * @param maximum the maximum number of Options for a valid CommandLine
     * @param required a flag whether this group is required
     */
    public GroupImpl(final List options, final String name, final String description, final int minimum,
            final int maximum, final boolean required) {
        super(0, required);

        this.name = name;
        this.description = description;
        this.minimum = minimum;
        this.maximum = maximum;

        // store a copy of the options to be used by the
        // help methods
        this.options = Collections.unmodifiableList(options);

        // anonymous Argument temporary storage
        final List newAnonymous = new ArrayList();

        // map (key=trigger & value=Option) temporary storage
        final SortedMap newOptionMap = new TreeMap(ReverseStringComparator.getInstance());

        // prefixes temporary storage
        final Set newPrefixes = new HashSet();

        // process the options
        for (final Iterator i = options.iterator(); i.hasNext();) {
            final Option option = (Option) i.next();
            option.setParent(this);

            if (option instanceof Argument) {
                i.remove();
                newAnonymous.add(option);
            } else {
                final Set triggers = option.getTriggers();

                for (Iterator j = triggers.iterator(); j.hasNext();) {
                    newOptionMap.put(j.next(), option);
                }

                // store the prefixes
                newPrefixes.addAll(option.getPrefixes());
            }
        }

        this.anonymous = Collections.unmodifiableList(newAnonymous);
        this.optionMap = Collections.unmodifiableSortedMap(newOptionMap);
        this.prefixes = Collections.unmodifiableSet(newPrefixes);
    }

    public boolean canProcess(final WriteableCommandLine commandLine, final String arg) {
        if (arg == null) {
            return false;
        }

        // if arg does not require bursting
        if (optionMap.containsKey(arg)) {
            return true;
        }

        // filter
        final Map tailMap = optionMap.tailMap(arg);

        // check if bursting is required
        for (final Iterator iter = tailMap.values().iterator(); iter.hasNext();) {
            final Option option = (Option) iter.next();

            if (option.canProcess(commandLine, arg)) {
                return true;
            }
        }

        if (looksLikeOption(commandLine, arg)) {
            return false;
        }

        // anonymous argument(s) means we can process it
        if (anonymous.size() > 0) {
            return true;
        }

        return false;
    }

    public Set getPrefixes() {
        return prefixes;
    }

    public Set getTriggers() {
        return optionMap.keySet();
    }

    public void process(final WriteableCommandLine commandLine, final ListIterator arguments)
            throws OptionException {
        String previous = null;

        // [START process each command line token
        while (arguments.hasNext()) {
            // grab the next argument
            final String arg = (String) arguments.next();

            // if we have just tried to process this instance
            if (arg == previous) {
                // rollback and abort
                arguments.previous();

                break;
            }

            // remember last processed instance
            previous = arg;

            final Option opt = (Option) optionMap.get(arg);

            // option found
            if (opt != null) {
                arguments.previous();
                opt.process(commandLine, arguments);
            }
            // [START option NOT found
            else {
                // it might be an anonymous argument continue search
                // [START argument may be anonymous
                if (looksLikeOption(commandLine, arg)) {
                    // narrow the search
                    final Collection values = optionMap.tailMap(arg).values();

                    boolean foundMemberOption = false;

                    for (Iterator i = values.iterator(); i.hasNext() && !foundMemberOption;) {
                        final Option option = (Option) i.next();

                        if (option.canProcess(commandLine, arg)) {
                            foundMemberOption = true;
                            arguments.previous();
                            option.process(commandLine, arguments);
                        }
                    }

                    // back track and abort this group if necessary
                    if (!foundMemberOption) {
                        arguments.previous();

                        return;
                    }
                } // [END argument may be anonymous

                // [START argument is NOT anonymous
                else {
                    // move iterator back, current value not used
                    arguments.previous();

                    // if there are no anonymous arguments then this group can't
                    // process the argument
                    if (anonymous.isEmpty()) {
                        break;
                    }

                    // TODO: why do we iterate over all anonymous arguments?
                    // canProcess will always return true?
                    for (final Iterator i = anonymous.iterator(); i.hasNext();) {
                        final Argument argument = (Argument) i.next();

                        if (argument.canProcess(commandLine, arguments)) {
                            argument.process(commandLine, arguments);
                        }
                    }
                } // [END argument is NOT anonymous
            } // [END option NOT found
        } // [END process each command line token
    }

    public void validate(final WriteableCommandLine commandLine) throws OptionException {
        // number of options found
        int present = 0;

        // reference to first unexpected option
        Option unexpected = null;

        for (final Iterator i = options.iterator(); i.hasNext();) {
            final Option option = (Option) i.next();

            // needs validation?
            boolean validate = option.isRequired();

            // if the child option is present then validate it
            if (commandLine.hasOption(option)) {
                if (++present > maximum) {
                    unexpected = option;

                    break;
                }
                validate = true;
            }

            if (validate) {
                option.validate(commandLine);
            }
        }

        // too many options
        if (unexpected != null) {
            throw new OptionException(this, ResourceConstants.UNEXPECTED_TOKEN, unexpected.getPreferredName());
        }

        // too few option
        if (present < minimum) {
            throw new OptionException(this, ResourceConstants.MISSING_OPTION);
        }

        // validate each anonymous argument
        for (final Iterator i = anonymous.iterator(); i.hasNext();) {
            final Option option = (Option) i.next();
            option.validate(commandLine);
        }
    }

    public String getPreferredName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public void appendUsage(final StringBuffer buffer, final Set helpSettings, final Comparator comp) {
        appendUsage(buffer, helpSettings, comp, "|");
    }

    public void appendUsage(final StringBuffer buffer, final Set helpSettings, final Comparator comp,
            final String separator) {
        final Set helpSettingsCopy = new HashSet(helpSettings);

        final boolean optional = !isRequired() && (helpSettingsCopy.contains(DisplaySetting.DISPLAY_OPTIONAL)
                || helpSettingsCopy.contains(DisplaySetting.DISPLAY_OPTIONAL_CHILD_GROUP));

        final boolean expanded = (name == null) || helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_EXPANDED);

        final boolean named = !expanded
                || ((name != null) && helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_NAME));

        final boolean arguments = helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_ARGUMENT);

        final boolean outer = helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_OUTER);

        helpSettingsCopy.remove(DisplaySetting.DISPLAY_GROUP_OUTER);

        final boolean both = named && expanded;

        if (optional) {
            buffer.append('[');
        }

        if (named) {
            buffer.append(name);
        }

        if (both) {
            buffer.append(" (");
        }

        if (expanded) {
            final Set childSettings;

            if (!helpSettingsCopy.contains(DisplaySetting.DISPLAY_GROUP_EXPANDED)) {
                childSettings = DisplaySetting.NONE;
            } else {
                childSettings = new HashSet(helpSettingsCopy);
                childSettings.remove(DisplaySetting.DISPLAY_OPTIONAL);
            }

            // grab a list of the group's options.
            final List list;

            if (comp == null) {
                // default to using the initial order
                list = options;
            } else {
                // sort options if comparator is supplied
                list = new ArrayList(options);
                Collections.sort(list, comp);
            }

            // for each option.
            for (final Iterator i = list.iterator(); i.hasNext();) {
                final Option option = (Option) i.next();

                // append usage information
                option.appendUsage(buffer, childSettings, comp);

                // add separators as needed
                if (i.hasNext()) {
                    buffer.append(separator);
                }
            }
        }

        if (both) {
            buffer.append(')');
        }

        if (optional && outer) {
            buffer.append(']');
        }

        if (arguments) {
            for (final Iterator i = anonymous.iterator(); i.hasNext();) {
                buffer.append(' ');

                final Option option = (Option) i.next();
                option.appendUsage(buffer, helpSettingsCopy, comp);
            }
        }

        if (optional && !outer) {
            buffer.append(']');
        }
    }

    public List helpLines(final int depth, final Set helpSettings, final Comparator comp) {
        final List helpLines = new ArrayList();

        if (helpSettings.contains(DisplaySetting.DISPLAY_GROUP_NAME)) {
            final HelpLine helpLine = new HelpLineImpl(this, depth);
            helpLines.add(helpLine);
        }

        if (helpSettings.contains(DisplaySetting.DISPLAY_GROUP_EXPANDED)) {
            // grab a list of the group's options.
            final List list;

            if (comp == null) {
                // default to using the initial order
                list = options;
            } else {
                // sort options if comparator is supplied
                list = new ArrayList(options);
                Collections.sort(list, comp);
            }

            // for each option
            for (final Iterator i = list.iterator(); i.hasNext();) {
                final Option option = (Option) i.next();
                helpLines.addAll(option.helpLines(depth + 1, helpSettings, comp));
            }
        }

        if (helpSettings.contains(DisplaySetting.DISPLAY_GROUP_ARGUMENT)) {
            for (final Iterator i = anonymous.iterator(); i.hasNext();) {
                final Option option = (Option) i.next();
                helpLines.addAll(option.helpLines(depth + 1, helpSettings, comp));
            }
        }

        return helpLines;
    }

    /**
     * Gets the member Options of thie Group.
     * Note this does not include any Arguments
     * @return only the non Argument Options of the Group
     */
    public List getOptions() {
        return options;
    }

    /**
     * Gets the anonymous Arguments of this Group.
     * @return the Argument options of this Group
     */
    public List getAnonymous() {
        return anonymous;
    }

    public Option findOption(final String trigger) {
        final Iterator i = getOptions().iterator();

        while (i.hasNext()) {
            final Option option = (Option) i.next();
            final Option found = option.findOption(trigger);

            if (found != null) {
                return found;
            }
        }

        return null;
    }

    public int getMinimum() {
        return minimum;
    }

    public int getMaximum() {
        return maximum;
    }

    /**
     * Tests whether this option is required. For groups we evaluate the
     * <code>required</code> flag common to all options, but also take the
     * minimum constraints into account.
     *
     * @return a flag whether this option is required
     */
    public boolean isRequired() {
        return (getParent() == null || super.isRequired()) && getMinimum() > 0;
    }

    public void defaults(final WriteableCommandLine commandLine) {
        super.defaults(commandLine);

        for (final Iterator i = options.iterator(); i.hasNext();) {
            final Option option = (Option) i.next();
            option.defaults(commandLine);
        }

        for (final Iterator i = anonymous.iterator(); i.hasNext();) {
            final Option option = (Option) i.next();
            option.defaults(commandLine);
        }
    }

    /**
     * Helper method for testing whether an element of the command line looks
     * like an option. This method queries the command line, but sets the
     * current option first.
     *
     * @param commandLine the command line
     * @param trigger the trigger to be checked
     * @return a flag whether this element looks like an option
     */
    private boolean looksLikeOption(final WriteableCommandLine commandLine, final String trigger) {
        Option oldOption = commandLine.getCurrentOption();
        try {
            commandLine.setCurrentOption(this);
            return commandLine.looksLikeOption(trigger);
        } finally {
            commandLine.setCurrentOption(oldOption);
        }
    }
}

class ReverseStringComparator implements Comparator {
    private static final Comparator instance = new ReverseStringComparator();

    private ReverseStringComparator() {
        // just making sure nobody else creates one
    }

    /**
     * Gets a singleton instance of a ReverseStringComparator
     * @return the singleton instance
     */
    public static final Comparator getInstance() {
        return instance;
    }

    public int compare(final Object o1, final Object o2) {
        final String s1 = (String) o1;
        final String s2 = (String) o2;

        return -s1.compareTo(s2);
    }
}