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}