001package org.unix4j.option;
002
003import java.util.Collection;
004import java.util.EnumSet;
005import java.util.Iterator;
006
007/**
008 * Default implementation of a modifiable option set. The option set is backed
009 * by an efficient {@link EnumSet}.
010 * 
011 * @param <E>
012 *            the option enum type
013 */
014public class DefaultOptionSet<E extends Enum<E> & Option> implements OptionSet<E>, Iterable<E>, Cloneable {
015
016        private final Class<E> optionType;
017        private EnumSet<E> options; // cannot be final because of clone
018        private EnumSet<E> useAcronym; // cannot be final because of clone
019
020        /**
021         * Constructor for option set initialized with a single option.
022         * 
023         * @param option
024         *            the option to be set
025         */
026        public DefaultOptionSet(E option) {
027                this.optionType = option.getDeclaringClass();
028                this.options = EnumSet.of(option);
029                this.useAcronym = EnumSet.noneOf(optionType);
030        }
031
032        /**
033         * Constructor for option set initialized with at least one active options.
034         * 
035         * @param first
036         *            the first option to be set
037         * @param rest
038         *            the remaining options to be set
039         */
040        @SuppressWarnings("unchecked")
041        public DefaultOptionSet(E first, E... rest) {
042                this.optionType = first.getDeclaringClass();
043                this.options = EnumSet.of(first, rest);
044                this.useAcronym = EnumSet.noneOf(optionType);
045        }
046
047        /**
048         * Constructor for an empty option set with no active options.
049         * 
050         * @param optionType
051         *            the option type class
052         */
053        public DefaultOptionSet(Class<E> optionType) {
054                this.optionType = optionType;
055                this.options = EnumSet.noneOf(optionType);
056                this.useAcronym = EnumSet.noneOf(optionType);
057        }
058
059        @Override
060        public Class<E> optionType() {
061                return optionType;
062        }
063
064        /**
065         * Sets the specified {@code option}.
066         * 
067         * @param option
068         *            the option to set
069         */
070        public void set(E option) {
071                options.add(option);
072        }
073
074        /**
075         * Sets all specified {@code options}.
076         * 
077         * @param options
078         *            the options to set
079         */
080        @SuppressWarnings("unchecked")
081        public void setAll(E... options) {
082                for (int i = 0; i < options.length; i++) {
083                        set(options[i]);
084                }
085        }
086
087        /**
088         * Sets all specified {@code options}.
089         * 
090         * @param options
091         *            the options to set
092         */
093        public void setAll(Collection<? extends E> options) {
094                for (final E option : options) {
095                        set(option);
096                        setUseAcronymFor(option, false);
097                }
098        }
099
100        /**
101         * Sets all the options contained in the specified {@code optionSet}.
102         * <p>
103         * Note that also the {@link #useAcronymFor(Option)} flags are also
104         * inherited from the specified {@code optionSet}.
105         * 
106         * @param optionSet
107         *            the optionSet with options to be set in this {@code OptionSet}
108         */
109        public void setAll(OptionSet<E> optionSet) {
110                options.addAll(optionSet.asSet());
111                for (E option : optionType.getEnumConstants()) {
112                        setUseAcronymFor(option, optionSet.useAcronymFor(option));
113                }
114        }
115
116        @Override
117        public boolean isSet(E option) {
118                return options.contains(option);
119        }
120
121        /**
122         * Returns the number of set options in this {@code OptionSet}
123         * 
124         * @return the number of set options
125         */
126        @Override
127        public int size() {
128                return options.size();
129        }
130
131        /**
132         * Returns true if no option is set.
133         * 
134         * @return true if no option is set.
135         */
136        public boolean isEmpty() {
137                return options.isEmpty();
138        }
139
140        /**
141         * Returns the underlying backing {@link EnumSet}. Changing the returned set
142         * will also alter this {@code OptionSet} and vice versa.
143         * 
144         * @return the underlying backing enum set
145         */
146        @Override
147        public EnumSet<E> asSet() {
148                return options;
149        }
150
151        /**
152         * Returns an iterator over all set options in this {@code OptionSet}.
153         * Removing an option from the
154         * 
155         * @return an iterator over all set options
156         */
157        @Override
158        public Iterator<E> iterator() {
159                return options.iterator();
160        }
161
162        @Override
163        public int hashCode() {
164                return options.hashCode();
165        }
166
167        @Override
168        public boolean equals(Object o) {
169                if (this == o)
170                        return true;
171                if (o instanceof DefaultOptionSet) {
172                        return options.equals(((DefaultOptionSet<?>) o).options);
173                }
174                return false;
175        }
176
177        @Override
178        public DefaultOptionSet<E> clone() {
179                try {
180                        @SuppressWarnings("unchecked")
181                        final DefaultOptionSet<E> clone = (DefaultOptionSet<E>) super.clone();
182                        clone.options = clone.options.clone();
183                        clone.useAcronym = clone.useAcronym.clone();
184                        return clone;
185                } catch (CloneNotSupportedException e) {
186                        throw new RuntimeException("should be cloneable", e);
187                }
188        }
189
190        @Override
191        public boolean useAcronymFor(E option) {
192                if (useAcronym.contains(option)) {
193                        if (options.contains(option)) {
194                                return true;
195                        }
196                        useAcronym.remove(option);
197                }
198                return false;
199        }
200
201        /**
202         * Sets the flag indicating whether string representations should use the
203         * {@link Option#acronym() acronym} instead of the long option
204         * {@link Option#name() name} for all options.
205         * 
206         * @param useAcronym
207         *            new flag value to set, true if the option acronym should be
208         *            used for all options
209         * @see #setUseAcronymFor(Enum, boolean)
210         */
211        public void setUseAcronymForAll(boolean useAcronym) {
212                if (useAcronym) {
213                        this.useAcronym.addAll(EnumSet.complementOf(this.useAcronym));
214                } else {
215                        this.useAcronym.removeAll(EnumSet.copyOf(this.useAcronym));
216                }
217        }
218
219        /**
220         * Sets the flag indicating whether string representations should use the
221         * {@link Option#acronym() acronym} instead of the long option
222         * {@link Option#name() name} for the specified {@code option}.
223         * 
224         * @param option
225         *            the option for which this flag is set
226         * @param useAcronym
227         *            new flag value to set, true if the option acronym should be
228         *            used for the specified {@code option}
229         * @see #setUseAcronymForAll(boolean)
230         */
231        public void setUseAcronymFor(E option, boolean useAcronym) {
232                if (useAcronym) {
233                        this.useAcronym.add(option);
234                } else {
235                        this.useAcronym.remove(option);
236                }
237        }
238
239        @Override
240        public String toString() {
241                return toString(this);
242        }
243
244        public static <O extends Option> String toString(OptionSet<O> optionSet) {
245                final StringBuilder sb = new StringBuilder();
246                // first, the options with acronym
247                for (final O opt : optionSet) {
248                        if (optionSet.useAcronymFor(opt)) {
249                                if (sb.length() == 0) {
250                                        sb.append("-");
251                                }
252                                sb.append(opt.acronym());
253                        }
254                }
255                // now all options with long names
256                for (final O opt : optionSet) {
257                        if (!optionSet.useAcronymFor(opt)) {
258                                if (sb.length() != 0) {
259                                        sb.append(" ");
260                                }
261                                sb.append("--").append(opt.name());
262                        }
263                }
264                return sb.toString();
265        }
266}