Add helper method to clear up intent.
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Options.java
1 /*
2  * Sone - Options.java - Copyright © 2010–2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.core;
19
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.Map;
23
24 import com.google.common.base.Predicate;
25
26 /**
27  * Stores various options that influence Sone’s behaviour.
28  *
29  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
30  */
31 public class Options {
32
33         /**
34          * Contains current and default value of an option.
35          *
36          * @param <T>
37          *            The type of the option
38          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
39          */
40         public static interface Option<T> {
41
42                 /**
43                  * Returns the default value of the option.
44                  *
45                  * @return The default value of the option
46                  */
47                 public T getDefault();
48
49                 /**
50                  * Returns the current value of the option. If the current value is not
51                  * set (usually {@code null}), the default value is returned.
52                  *
53                  * @return The current value of the option
54                  */
55                 public T get();
56
57                 /**
58                  * Returns the real value of the option. This will also return an unset
59                  * value (usually {@code null})!
60                  *
61                  * @return The real value of the option
62                  */
63                 public T getReal();
64
65                 /**
66                  * Validates the given value. Note that {@code null} is always a valid
67                  * value!
68                  *
69                  * @param value
70                  *            The value to validate
71                  * @return {@code true} if this option does not have a validator, or the
72                  *         validator validates this object, {@code false} otherwise
73                  */
74                 public boolean validate(T value);
75
76                 /**
77                  * Sets the current value of the option.
78                  *
79                  * @param value
80                  *            The new value of the option
81                  * @throws IllegalArgumentException
82                  *             if the value is not valid for this option
83                  */
84                 public void set(T value) throws IllegalArgumentException;
85
86         }
87
88         /**
89          * Interface for objects that want to be notified when an option changes its
90          * value.
91          *
92          * @param <T>
93          *            The type of the option
94          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
95          */
96         public static interface OptionWatcher<T> {
97
98                 /**
99                  * Notifies an object that an option has been changed.
100                  *
101                  * @param option
102                  *            The option that has changed
103                  * @param oldValue
104                  *            The old value of the option
105                  * @param newValue
106                  *            The new value of the option
107                  */
108                 public void optionChanged(Option<T> option, T oldValue, T newValue);
109
110         }
111
112         /**
113          * Basic implementation of an {@link Option} that notifies an
114          * {@link OptionWatcher} if the value changes.
115          *
116          * @param <T>
117          *            The type of the option
118          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
119          */
120         public static class DefaultOption<T> implements Option<T> {
121
122                 /** The default value. */
123                 private final T defaultValue;
124
125                 /** The current value. */
126                 private volatile T value;
127
128                 /** The validator. */
129                 private Predicate<T> validator;
130
131                 /** The option watcher. */
132                 private final OptionWatcher<T> optionWatcher;
133
134                 /**
135                  * Creates a new default option.
136                  *
137                  * @param defaultValue
138                  *            The default value of the option
139                  */
140                 public DefaultOption(T defaultValue) {
141                         this(defaultValue, (OptionWatcher<T>) null);
142                 }
143
144                 /**
145                  * Creates a new default option.
146                  *
147                  * @param defaultValue
148                  *            The default value of the option
149                  * @param validator
150                  *            The validator for value validation (may be {@code null})
151                  */
152                 public DefaultOption(T defaultValue, Predicate<T> validator) {
153                         this(defaultValue, validator, null);
154                 }
155
156                 /**
157                  * Creates a new default option.
158                  *
159                  * @param defaultValue
160                  *            The default value of the option
161                  * @param optionWatchers
162                  *            The option watchers (may be {@code null})
163                  */
164                 public DefaultOption(T defaultValue, OptionWatcher<T> optionWatchers) {
165                         this(defaultValue, null, optionWatchers);
166                 }
167
168                 /**
169                  * Creates a new default option.
170                  *
171                  * @param defaultValue
172                  *            The default value of the option
173                  * @param validator
174                  *            The validator for value validation (may be {@code null})
175                  * @param optionWatcher
176                  *            The option watcher (may be {@code null})
177                  */
178                 public DefaultOption(T defaultValue, Predicate<T> validator, OptionWatcher<T> optionWatcher) {
179                         this.defaultValue = defaultValue;
180                         this.validator = validator;
181                         this.optionWatcher = optionWatcher;
182                 }
183
184                 @Override
185                 public T getDefault() {
186                         return defaultValue;
187                 }
188
189                 @Override
190                 public T get() {
191                         return (value != null) ? value : defaultValue;
192                 }
193
194                 @Override
195                 public T getReal() {
196                         return value;
197                 }
198
199                 @Override
200                 public boolean validate(T value) {
201                         return (validator == null) || (value == null) || validator.apply(value);
202                 }
203
204                 @Override
205                 public void set(T value) {
206                         if ((value != null) && (validator != null) && (!validator.apply(value))) {
207                                 throw new IllegalArgumentException("New Value (" + value + ") could not be validated.");
208                         }
209                         T oldValue = this.value;
210                         this.value = value;
211                         if (!get().equals(oldValue)) {
212                                 if (optionWatcher != null) {
213                                         optionWatcher.optionChanged(this, oldValue, get());
214                                 }
215                         }
216                 }
217
218         }
219
220         /** Holds all {@link Boolean} {@link Option}s. */
221         private final Map<String, Option<Boolean>> booleanOptions = Collections.synchronizedMap(new HashMap<String, Option<Boolean>>());
222
223         /** Holds all {@link Integer} {@link Option}s. */
224         private final Map<String, Option<Integer>> integerOptions = Collections.synchronizedMap(new HashMap<String, Option<Integer>>());
225
226         /** Holds all {@link String} {@link Option}s. */
227         private final Map<String, Option<String>> stringOptions = Collections.synchronizedMap(new HashMap<String, Option<String>>());
228
229         /** Holds all {@link Enum} {@link Option}s. */
230         private final Map<String, Option<? extends Enum<?>>> enumOptions = Collections.synchronizedMap(new HashMap<String, Option<? extends Enum<?>>>());
231
232         /**
233          * Adds a boolean option.
234          *
235          * @param name
236          *            The name of the option
237          * @param booleanOption
238          *            The option
239          * @return The given option
240          */
241         public Option<Boolean> addBooleanOption(String name, Option<Boolean> booleanOption) {
242                 booleanOptions.put(name, booleanOption);
243                 return booleanOption;
244         }
245
246         /**
247          * Returns the boolean option with the given name.
248          *
249          * @param name
250          *            The name of the option
251          * @return The option, or {@code null} if there is no option with the given
252          *         name
253          */
254         public Option<Boolean> getBooleanOption(String name) {
255                 return booleanOptions.get(name);
256         }
257
258         /**
259          * Adds an {@link Integer} {@link Option}.
260          *
261          * @param name
262          *            The name of the option
263          * @param integerOption
264          *            The option
265          * @return The given option
266          */
267         public Option<Integer> addIntegerOption(String name, Option<Integer> integerOption) {
268                 integerOptions.put(name, integerOption);
269                 return integerOption;
270         }
271
272         /**
273          * Returns an {@link Integer} {@link Option}.
274          *
275          * @param name
276          *            The name of the integer option to get
277          * @return The integer option, or {@code null} if there is no option with
278          *         the given name
279          */
280         public Option<Integer> getIntegerOption(String name) {
281                 return integerOptions.get(name);
282         }
283
284         /**
285          * Adds a {@link String} {@link Option}.
286          *
287          * @param name
288          *            The name of the option
289          * @param stringOption
290          *            The option
291          * @return The given option
292          */
293         public Option<String> addStringOption(String name, Option<String> stringOption) {
294                 stringOptions.put(name, stringOption);
295                 return stringOption;
296         }
297
298         /**
299          * Returns a {@link String} {@link Option}.
300          *
301          * @param name
302          *            The name of the string option to get
303          * @return The string option, or {@code null} if there is no option with the
304          *         given name
305          */
306         public Option<String> getStringOption(String name) {
307                 return stringOptions.get(name);
308         }
309
310         /**
311          * Adds an {@link Enum} {@link Option}.
312          *
313          * @param <T>
314          *            The enum type
315          * @param name
316          *            The name of the option
317          * @param enumOption
318          *            The option
319          * @return The given option
320          */
321         public <T extends Enum<T>> Option<T> addEnumOption(String name, Option<T> enumOption) {
322                 enumOptions.put(name, enumOption);
323                 return enumOption;
324         }
325
326         /**
327          * Returns a {@link Enum} {@link Option}. As the type can probably not be
328          * interred correctly you could help the compiler by calling this method
329          * like this:
330          * <p>
331          *
332          * <pre>
333          * options.&lt;SomeEnum&gt; getEnumOption(&quot;SomeEnumOption&quot;).get();
334          * </pre>
335          *
336          * @param <T>
337          *            The enum type
338          * @param name
339          *            The name of the option
340          * @return The enum option, or {@code null} if there is no enum option with
341          *         the given name
342          */
343         @SuppressWarnings("unchecked")
344         public <T extends Enum<T>> Option<T> getEnumOption(String name) {
345                 return (Option<T>) enumOptions.get(name);
346         }
347
348 }