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 true} if 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 null} if 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 null} if 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 true} if 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 true} if 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