Merge branch 'partial-rewrite' into next
[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                 /**
185                  * {@inheritDoc}
186                  */
187                 @Override
188                 public T getDefault() {
189                         return defaultValue;
190                 }
191
192                 /**
193                  * {@inheritDoc}
194                  */
195                 @Override
196                 public T get() {
197                         return (value != null) ? value : defaultValue;
198                 }
199
200                 /**
201                  * Returns the real value of the option. This will also return an unset
202                  * value (usually {@code null})!
203                  *
204                  * @return The real value of the option
205                  */
206                 @Override
207                 public T getReal() {
208                         return value;
209                 }
210
211                 /**
212                  * {@inheritDoc}
213                  */
214                 @Override
215                 public boolean validate(T value) {
216                         return (validator == null) || (value == null) || validator.apply(value);
217                 }
218
219                 /**
220                  * {@inheritDoc}
221                  */
222                 @Override
223                 public void set(T value) {
224                         if ((value != null) && (validator != null) && (!validator.apply(value))) {
225                                 throw new IllegalArgumentException("New Value (" + value + ") could not be validated.");
226                         }
227                         T oldValue = this.value;
228                         this.value = value;
229                         if (!get().equals(oldValue)) {
230                                 if (optionWatcher != null) {
231                                         optionWatcher.optionChanged(this, oldValue, get());
232                                 }
233                         }
234                 }
235
236         }
237
238         /** Holds all {@link Boolean} {@link Option}s. */
239         private final Map<String, Option<Boolean>> booleanOptions = Collections.synchronizedMap(new HashMap<String, Option<Boolean>>());
240
241         /** Holds all {@link Integer} {@link Option}s. */
242         private final Map<String, Option<Integer>> integerOptions = Collections.synchronizedMap(new HashMap<String, Option<Integer>>());
243
244         /** Holds all {@link String} {@link Option}s. */
245         private final Map<String, Option<String>> stringOptions = Collections.synchronizedMap(new HashMap<String, Option<String>>());
246
247         /** Holds all {@link Enum} {@link Option}s. */
248         private final Map<String, Option<? extends Enum<?>>> enumOptions = Collections.synchronizedMap(new HashMap<String, Option<? extends Enum<?>>>());
249
250         /**
251          * Adds a boolean option.
252          *
253          * @param name
254          *            The name of the option
255          * @param booleanOption
256          *            The option
257          * @return The given option
258          */
259         public Option<Boolean> addBooleanOption(String name, Option<Boolean> booleanOption) {
260                 booleanOptions.put(name, booleanOption);
261                 return booleanOption;
262         }
263
264         /**
265          * Returns the boolean option with the given name.
266          *
267          * @param name
268          *            The name of the option
269          * @return The option, or {@code null} if there is no option with the given
270          *         name
271          */
272         public Option<Boolean> getBooleanOption(String name) {
273                 return booleanOptions.get(name);
274         }
275
276         /**
277          * Adds an {@link Integer} {@link Option}.
278          *
279          * @param name
280          *            The name of the option
281          * @param integerOption
282          *            The option
283          * @return The given option
284          */
285         public Option<Integer> addIntegerOption(String name, Option<Integer> integerOption) {
286                 integerOptions.put(name, integerOption);
287                 return integerOption;
288         }
289
290         /**
291          * Returns an {@link Integer} {@link Option}.
292          *
293          * @param name
294          *            The name of the integer option to get
295          * @return The integer option, or {@code null} if there is no option with
296          *         the given name
297          */
298         public Option<Integer> getIntegerOption(String name) {
299                 return integerOptions.get(name);
300         }
301
302         /**
303          * Adds a {@link String} {@link Option}.
304          *
305          * @param name
306          *            The name of the option
307          * @param stringOption
308          *            The option
309          * @return The given option
310          */
311         public Option<String> addStringOption(String name, Option<String> stringOption) {
312                 stringOptions.put(name, stringOption);
313                 return stringOption;
314         }
315
316         /**
317          * Returns a {@link String} {@link Option}.
318          *
319          * @param name
320          *            The name of the string option to get
321          * @return The string option, or {@code null} if there is no option with the
322          *         given name
323          */
324         public Option<String> getStringOption(String name) {
325                 return stringOptions.get(name);
326         }
327
328         /**
329          * Adds an {@link Enum} {@link Option}.
330          *
331          * @param <T>
332          *            The enum type
333          * @param name
334          *            The name of the option
335          * @param enumOption
336          *            The option
337          * @return The given option
338          */
339         public <T extends Enum<T>> Option<T> addEnumOption(String name, Option<T> enumOption) {
340                 enumOptions.put(name, enumOption);
341                 return enumOption;
342         }
343
344         /**
345          * Returns a {@link Enum} {@link Option}. As the type can probably not be
346          * interred correctly you could help the compiler by calling this method
347          * like this:
348          * <p>
349          *
350          * <pre>
351          * options.&lt;SomeEnum&gt; getEnumOption(&quot;SomeEnumOption&quot;).get();
352          * </pre>
353          *
354          * @param <T>
355          *            The enum type
356          * @param name
357          *            The name of the option
358          * @return The enum option, or {@code null} if there is no enum option with
359          *         the given name
360          */
361         @SuppressWarnings("unchecked")
362         public <T extends Enum<T>> Option<T> getEnumOption(String name) {
363                 return (Option<T>) enumOptions.get(name);
364         }
365
366 }