1 /*
2  * JBoss, Home of Professional Open Source
3  *
4  * Copyright 2009 Red Hat, Inc. and/or its affiliates.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19 package org.xnio;
20
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.ArrayList;
24 import java.util.Map;
25 import java.util.Collections;
26 import java.util.IdentityHashMap;
27 import java.util.Properties;
28 import java.io.Serializable;
29
30 import static org.xnio._private.Messages.msg;
31 import static org.xnio._private.Messages.optionParseMsg;
32
33 /**
34  * An immutable map of options to option values.  No {@code null} keys or values are permitted.
35  */

36 public final class OptionMap implements Iterable<Option<?>>, Serializable {
37
38     private static final long serialVersionUID = 3632842565346928132L;
39
40     private final Map<Option<?>, Object> value;
41
42     private OptionMap(final Map<Option<?>, Object> value) {
43         this.value = value;
44     }
45
46     /**
47      * Determine whether this option map contains the given option.
48      *
49      * @param option the option to check
50      * @return {@code trueif the option is present in the option map
51      */

52     public boolean contains(Option<?> option) {
53         return value.containsKey(option);
54     }
55
56     /**
57      * Get the value of an option from this option map.
58      *
59      * @param option the option to get
60      * @param <T> the type of the option
61      * @return the option value, or {@code nullif it is not present
62      */

63     public <T> T get(Option<T> option) {
64         return option.cast(value.get(option));
65     }
66
67     /**
68      * Get the value of an option from this option map, with a specified default if the value is missing.
69      *
70      * @param option the option to get
71      * @param defaultValue the value to return if the option is not set
72      * @param <T> the type of the option
73      * @return the option value, or {@code nullif it is not present
74      */

75     public <T> T get(Option<T> option, T defaultValue) {
76         final Object o = value.get(option);
77         return o == null ? defaultValue : option.cast(o);
78     }
79
80     /**
81      * Get a boolean value from this option map, with a specified default if the value is missing.
82      *
83      * @param option the option to get
84      * @param defaultValue the default value if the option is not present
85      * @return the result
86      */

87     public boolean get(Option<Boolean> option, boolean defaultValue) {
88         final Object o = value.get(option);
89         return o == null ? defaultValue : option.cast(o).booleanValue();
90     }
91
92     /**
93      * Get a int value from this option map, with a specified default if the value is missing.
94      *
95      * @param option the option to get
96      * @param defaultValue the default value if the option is not present
97      * @return the result
98      */

99     public int get(Option<Integer> option, int defaultValue) {
100         final Object o = value.get(option);
101         return o == null ? defaultValue : option.cast(o).intValue();
102     }
103
104     /**
105      * Get a long value from this option map, with a specified default if the value is missing.
106      *
107      * @param option the option to get
108      * @param defaultValue the default value if the option is not present
109      * @return the result
110      */

111     public long get(Option<Long> option, long defaultValue) {
112         final Object o = value.get(option);
113         return o == null ? defaultValue : option.cast(o).longValue();
114     }
115
116     /**
117      * Iterate over the options in this map.
118      *
119      * @return an iterator over the options
120      */

121     public Iterator<Option<?>> iterator() {
122         return Collections.unmodifiableCollection(value.keySet()).iterator();
123     }
124
125     /**
126      * Get the number of options stored in this map.
127      *
128      * @return the number of options
129      */

130     public int size() {
131         return value.size();
132     }
133
134     /**
135      * The empty option map.
136      */

137     public static final OptionMap EMPTY = new OptionMap(Collections.<Option<?>, Object>emptyMap());
138
139     /**
140      * Create a new builder.
141      *
142      * @return a new builder
143      */

144     public static Builder builder() {
145         return new Builder();
146     }
147
148     /**
149      * Create a single-valued option map.
150      *
151      * @param option the option to put in the map
152      * @param value the option value
153      * @param <T> the option value type
154      * @return the option map
155      *
156      * @since 3.0
157      */

158     public static <T> OptionMap create(Option<T> option, T value) {
159         if (option == null) {
160             throw msg.nullParameter("option");
161         }
162         if (value == null) {
163             throw msg.nullParameter("value");
164         }
165         return new OptionMap(Collections.<Option<?>, Object>singletonMap(option, option.cast(value)));
166     }
167
168     /**
169      * Create a two-valued option map.  If both options are the same key, then only the second one is added
170      * to the map.
171      *
172      * @param option1 the first option to put in the map
173      * @param value1 the first option value
174      * @param option2 the second option to put in the map
175      * @param value2 the second option value
176      * @param <T1> the first option value type
177      * @param <T2> the second option value type
178      * @return the option map
179      *
180      * @since 3.0
181      */

182     public static <T1, T2> OptionMap create(Option<T1> option1, T1 value1, Option<T2> option2, T2 value2) {
183         if (option1 == null) {
184             throw msg.nullParameter("option1");
185         }
186         if (value1 == null) {
187             throw msg.nullParameter("value1");
188         }
189         if (option2 == null) {
190             throw msg.nullParameter("option2");
191         }
192         if (value2 == null) {
193             throw msg.nullParameter("value2");
194         }
195         if (option1 == option2) {
196             return create(option2, value2);
197         }
198         final IdentityHashMap<Option<?>, Object> map = new IdentityHashMap<Option<?>, Object>(2);
199         map.put(option1, value1);
200         map.put(option2, value2);
201         return new OptionMap(map);
202     }
203
204     public String toString() {
205         final StringBuilder builder = new StringBuilder();
206         builder.append('{');
207         final Iterator<Map.Entry<Option<?>, Object>> iterator = value.entrySet().iterator();
208         while (iterator.hasNext()) {
209             final Map.Entry<Option<?>, Object> entry = iterator.next();
210             builder.append(entry.getKey()).append("=>").append(entry.getValue());
211             if (iterator.hasNext()) {
212                 builder.append(',');
213             }
214         }
215         builder.append('}');
216         return builder.toString();
217     }
218
219     /**
220      * Determine whether this option map is equal to another.
221      *
222      * @param other the other option map
223      * @return {@code trueif they are equal, {@code false} otherwise
224      */

225     public boolean equals(Object other) {
226         return other instanceof OptionMap && equals((OptionMap)other);
227     }
228
229     /**
230      * Determine whether this option map is equal to another.
231      *
232      * @param other the other option map
233      * @return {@code trueif they are equal, {@code false} otherwise
234      */

235     public boolean equals(OptionMap other) {
236         return this == other || other != null && value.equals(other.value);
237     }
238
239     /**
240      * Get the hash code for this option map.
241      *
242      * @return the hash code
243      */

244     public int hashCode() {
245         return value.hashCode();
246     }
247
248     /**
249      * A builder for immutable option maps.  Create an instance with the {@link OptionMap#builder()} method.
250      */

251     public static final class Builder {
252
253         private Builder() {
254         }
255
256         private static class OVPair<T> {
257             Option<T> option;
258             T value;
259
260             private OVPair(final Option<T> option, final T value) {
261                 this.option = option;
262                 this.value = value;
263             }
264         }
265
266         private List<OVPair<?>> list = new ArrayList<OVPair<?>>();
267
268         /**
269          * Set a key-value pair, parsing the value from the given string.
270          *
271          * @param key the key
272          * @param stringValue the string value
273          * @param <T> the option type
274          * @return this builder
275          */

276         public <T> Builder parse(Option<T> key, String stringValue) {
277             set(key, key.parseValue(stringValue, key.getClass().getClassLoader()));
278             return this;
279         }
280
281         /**
282          * Set a key-value pair, parsing the value from the given string.
283          *
284          * @param key the key
285          * @param stringValue the string value
286          * @param classLoader the class loader to use for parsing the value
287          * @param <T> the option type
288          * @return this builder
289          */

290         public <T> Builder parse(Option<T> key, String stringValue, ClassLoader classLoader) {
291             set(key, key.parseValue(stringValue, classLoader));
292             return this;
293         }
294
295         /**
296          * Add all options from a properties file.  Finds all entries which start with a given prefix followed by '.';
297          * the remainder of the property key (after the prefix) is the option name, and the value is the option value.
298          * <p>If the prefix does not end with '.' character, a '.' will be appended to it before parsing.
299          *
300          * @param props the properties to read
301          * @param prefix the prefix
302          * @param optionClassLoader the class loader to use to resolve option names
303          * @return this builder
304          */

305         public Builder parseAll(Properties props, String prefix, ClassLoader optionClassLoader) {
306             if (! prefix.endsWith(".")) {
307                 prefix = prefix + ".";
308             }
309             for (String name : props.stringPropertyNames()) {
310                 if (name.startsWith(prefix)) {
311                     final String optionName = name.substring(prefix.length());
312                     try {
313                         final Option<?> option = Option.fromString(optionName, optionClassLoader);
314                         parse(option, props.getProperty(name), optionClassLoader);
315                     } catch (IllegalArgumentException e) {
316                         optionParseMsg.invalidOptionInProperty(optionName, name, e);
317                     }
318                 }
319             }
320             return this;
321         }
322
323         /**
324          * Add all options from a properties file.  Finds all entries which start with a given prefix followed by '.';
325          * the remainder of the property key (after the prefix) is the option name, and the value is the option value.
326          *<p>If the prefix does not end with '.' character, a '.' will be appended to it before parsing.
327          *
328          * @param props the properties to read
329          * @param prefix the prefix
330          * @return this builder
331          */

332         public Builder parseAll(Properties props, String prefix) {
333             if (! prefix.endsWith(".")) {
334                 prefix = prefix + ".";
335             }
336             for (String name : props.stringPropertyNames()) {
337                 if (name.startsWith(prefix)) {
338                     final String optionName = name.substring(prefix.length());
339                     try {
340                         final Option<?> option = Option.fromString(optionName, getClass().getClassLoader());
341                         parse(option, props.getProperty(name));
342                     } catch (IllegalArgumentException e) {
343                         optionParseMsg.invalidOptionInProperty(optionName, name, e);
344                     }
345                 }
346             }
347             return this;
348         }
349
350         /**
351          * Set a key-value pair.
352          *
353          * @param key the key
354          * @param value the value
355          * @param <T> the option type
356          * @return this builder
357          */

358         public <T> Builder set(Option<T> key, T value) {
359             if (key == null) {
360                 throw msg.nullParameter("key");
361             }
362             if (value == null) {
363                 throw msg.nullParameter("value");
364             }
365             list.add(new OVPair<T>(key, value));
366             return this;
367         }
368
369         /**
370          * Set an int value for an Integer key.
371          *
372          * @param key the option
373          * @param value the value
374          * @return this builder
375          */

376         public Builder set(Option<Integer> key, int value) {
377             if (key == null) {
378                 throw msg.nullParameter("key");
379             }
380             list.add(new OVPair<Integer>(key, Integer.valueOf(value)));
381             return this;
382         }
383
384         /**
385          * Set int values for an Integer sequence key.
386          *
387          * @param key the key
388          * @param values the values
389          * @return this builder
390          */

391         public Builder setSequence(Option<Sequence<Integer>> key, int... values) {
392             if (key == null) {
393                 throw msg.nullParameter("key");
394             }
395             Integer[] a = new Integer[values.length];
396             for (int i = 0; i < values.length; i++) {
397                 a[i] = Integer.valueOf(values[i]);
398             }
399             list.add(new OVPair<Sequence<Integer>>(key, Sequence.of(a)));
400             return this;
401         }
402
403         /**
404          * Set a long value for a Long key.
405          *
406          * @param key the option
407          * @param value the value
408          * @return this builder
409          */

410         public Builder set(Option<Long> key, long value) {
411             if (key == null) {
412                 throw msg.nullParameter("key");
413             }
414             list.add(new OVPair<Long>(key, Long.valueOf(value)));
415             return this;
416         }
417
418         /**
419          * Set long values for a Long sequence key.
420          *
421          * @param key the key
422          * @param values the values
423          * @return this builder
424          */

425         public Builder setSequence(Option<Sequence<Long>> key, long... values) {
426             if (key == null) {
427                 throw msg.nullParameter("key");
428             }
429             Long[] a = new Long[values.length];
430             for (int i = 0; i < values.length; i++) {
431                 a[i] = Long.valueOf(values[i]);
432             }
433             list.add(new OVPair<Sequence<Long>>(key, Sequence.of(a)));
434             return this;
435         }
436
437         /**
438          * Set a boolean value for a Boolean key.
439          *
440          * @param key the option
441          * @param value the value
442          * @return this builder
443          */

444         public Builder set(Option<Boolean> key, boolean value) {
445             if (key == null) {
446                 throw msg.nullParameter("key");
447             }
448             list.add(new OVPair<Boolean>(key, Boolean.valueOf(value)));
449             return this;
450         }
451
452
453         /**
454          * Set boolean values for an Boolean sequence key.
455          *
456          * @param key the key
457          * @param values the values
458          * @return this builder
459          */

460         public Builder setSequence(Option<Sequence<Boolean>> key, boolean... values) {
461             if (key == null) {
462                 throw msg.nullParameter("key");
463             }
464             Boolean[] a = new Boolean[values.length];
465             for (int i = 0; i < values.length; i++) {
466                 a[i] = Boolean.valueOf(values[i]);
467             }
468             list.add(new OVPair<Sequence<Boolean>>(key, Sequence.of(a)));
469             return this;
470         }
471
472         /**
473          * Set a key-value pair, where the value is a sequence type.
474          *
475          * @param key the key
476          * @param values the values
477          * @param <T> the option type
478          * @return this builder
479          */

480         public <T> Builder setSequence(Option<Sequence<T>> key, T... values) {
481             if (key == null) {
482                 throw msg.nullParameter("key");
483             }
484             list.add(new OVPair<Sequence<T>>(key, Sequence.of(values)));
485             return this;
486         }
487
488         private <T> void copy(Map<?, ?> map, Option<T> option) {
489             set(option, option.cast(map.get(option)));
490         }
491
492         /**
493          * Add all the entries of a map.  Any keys of the map which are not valid {@link Option}s, or whose
494          * values are not valid arguments for the given {@code Option}, will cause an exception to be thrown.
495          * Any keys which occur more than once in this builder will be overwritten with the last occurring value.
496          *
497          * @param map the map
498          * @return this builder
499          * @throws ClassCastException if any entries of the map are not valid option-value pairs
500          */

501         public Builder add(Map<?, ?> map) throws ClassCastException {
502             for (Object key : map.keySet()) {
503                 final Option<?> option = Option.class.cast(key);
504                 copy(map, option);
505             }
506             return this;
507         }
508
509         private <T> void copy(OptionMap optionMap, Option<T> option) {
510             set(option, optionMap.get(option));
511         }
512
513         /**
514          * Add all entries from an existing option map to the one being built.
515          * Any keys which occur more than once in this builder will be overwritten with the last occurring value.
516          *
517          * @param optionMap the original option map
518          * @return this builder
519          */

520         public Builder addAll(OptionMap optionMap) {
521             for (Option<?> option : optionMap) {
522                 copy(optionMap, option);
523             }
524             return this;
525         }
526
527         /**
528          * Build a map that reflects the current state of this builder.
529          *
530          * @return the new immutable option map
531          */

532         public OptionMap getMap() {
533             final List<OVPair<?>> list = this.list;
534             if (list.size() == 0) {
535                 return EMPTY;
536             } else if (list.size() == 1) {
537                 final OVPair<?> pair = list.get(0);
538                 return new OptionMap(Collections.<Option<?>, Object>singletonMap(pair.option, pair.value));
539             } else {
540                 final Map<Option<?>, Object> map = new IdentityHashMap<Option<?>, Object>();
541                 for (OVPair<?> ovPair : list) {
542                     map.put(ovPair.option, ovPair.value);
543                 }
544                 return new OptionMap(map);
545             }
546         }
547     }
548 }
549