1 /*
2  * JBoss, Home of Professional Open Source
3  *
4  * Copyright 2010 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.io.InvalidObjectException;
22 import java.io.ObjectStreamException;
23 import java.io.Serializable;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.Modifier;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 import static org.xnio._private.Messages.msg;
36
37 /**
38  * A strongly-typed option to configure an aspect of a service or connection.  Options are immutable and use identity comparisons
39  * and hash codes.  Options should always be declared as <code>public static final</code> members in order to support serialization.
40  *
41  * @param <T> the option value type
42  */

43 public abstract class Option<T> implements Serializable {
44
45     private static final long serialVersionUID = -1564427329140182760L;
46
47     private final Class<?> declClass;
48     private final String name;
49
50     Option(final Class<?> declClass, final String name) {
51         if (declClass == null) {
52             throw msg.nullParameter("declClass");
53         }
54         if (name == null) {
55             throw msg.nullParameter("name");
56         }
57         this.declClass = declClass;
58         this.name = name;
59     }
60
61     /**
62      * Create an option with a simple type.  The class object given <b>must</b> represent some immutable type, otherwise
63      * unexpected behavior may result.
64      *
65      * @param declClass the declaring class of the option
66      * @param name the (field) name of this option
67      * @param type the class of the value associated with this option
68      * @return the option instance
69      */

70     public static <T> Option<T> simple(final Class<?> declClass, final String name, final Class<T> type) {
71         return new SingleOption<T>(declClass, name, type);
72     }
73
74     /**
75      * Create an option with a sequence type.  The class object given <b>must</b> represent some immutable type, otherwise
76      * unexpected behavior may result.
77      *
78      * @param declClass the declaring class of the option
79      * @param name the (field) name of this option
80      * @param elementType the class of the sequence element value associated with this option
81      * @return the option instance
82      */

83     public static <T> Option<Sequence<T>> sequence(final Class<?> declClass, final String name, final Class<T> elementType) {
84         return new SequenceOption<T>(declClass, name, elementType);
85     }
86
87     /**
88      * Create an option with a class type.  The class object given may represent any type.
89      *
90      * @param declClass the declaring class of the option
91      * @param name the (field) name of this option
92      * @param declType the class object for the type of the class object given
93      * @param <T> the type of the class object given
94      * @return the option instance
95      */

96     public static <T> Option<Class<? extends T>> type(final Class<?> declClass, final String name, final Class<T> declType) {
97         return new TypeOption<T>(declClass, name, declType);
98     }
99
100     /**
101      * Create an option with a sequence-of-types type.  The class object given may represent any type.
102      *
103      * @param declClass the declaring class of the option
104      * @param name the (field) name of this option
105      * @param elementDeclType the class object for the type of the sequence element class object given
106      * @param <T> the type of the sequence element class object given
107      * @return the option instance
108      */

109     public static <T> Option<Sequence<Class<? extends T>>> typeSequence(final Class<?> declClass, final String name, final Class<T> elementDeclType) {
110         return new TypeSequenceOption<T>(declClass, name, elementDeclType);
111     }
112
113     /**
114      * Get the name of this option.
115      *
116      * @return the option name
117      */

118     public String getName() {
119         return name;
120     }
121
122     /**
123      * Get a human-readable string representation of this object.
124      *
125      * @return the string representation
126      */

127     public String toString() {
128         return declClass.getName() + "." + name;
129     }
130
131     /**
132      * Get an option from a string name, using the given classloader.  If the classloader is {@code null}, the bootstrap
133      * classloader will be used.
134      *
135      * @param name the option string
136      * @param classLoader the class loader
137      * @return the option
138      * @throws IllegalArgumentException if the given option name is not valid
139      */

140     public static Option<?> fromString(String name, ClassLoader classLoader) throws IllegalArgumentException {
141         final int lastDot = name.lastIndexOf('.');
142         if (lastDot == -1) {
143             throw msg.invalidOptionName(name);
144         }
145         final String fieldName = name.substring(lastDot + 1);
146         final String className = name.substring(0, lastDot);
147         final Class<?> clazz;
148         try {
149             clazz = Class.forName(className, true, classLoader);
150         } catch (ClassNotFoundException e) {
151             throw msg.optionClassNotFound(className, classLoader);
152         }
153         final Field field;
154         try {
155             field = clazz.getField(fieldName);
156         } catch (NoSuchFieldException e) {
157             throw msg.noField(fieldName, clazz);
158         }
159         final int modifiers = field.getModifiers();
160         if (! Modifier.isPublic(modifiers)) {
161             throw msg.fieldNotAccessible(fieldName, clazz);
162         }
163         if (! Modifier.isStatic(modifiers)) {
164             throw msg.fieldNotStatic(fieldName, clazz);
165         }
166         final Option<?> option;
167         try {
168             option = (Option<?>) field.get(null);
169         } catch (IllegalAccessException e) {
170             throw msg.fieldNotAccessible(fieldName, clazz);
171         }
172         if (option == null) {
173             throw msg.invalidNullOption(name);
174         }
175         return option;
176     }
177
178     /**
179      * Return the given object as the type of this option.  If the cast could not be completed, an exception is thrown.
180      *
181      * @param o the object to cast
182      * @return the cast object
183      * @throws ClassCastException if the object is not of a compatible type
184      */

185     public abstract T cast(Object o) throws ClassCastException;
186
187     /**
188      * Return the given object as the type of this option.  If the cast could not be completed, an exception is thrown.
189      *
190      * @param o the object to cast
191      * @param defaultVal the value to return if {@code o} is {@code null}
192      *
193      * @return the cast object
194      *
195      * @throws ClassCastException if the object is not of a compatible type
196      */

197     public final T cast(Object o, T defaultVal) throws ClassCastException {
198         return o == null ? defaultVal : cast(o);
199     }
200
201     /**
202      * Parse a string value for this option.
203      *
204      * @param string the string
205      * @param classLoader the class loader to use to parse the value
206      * @return the parsed value
207      * @throws IllegalArgumentException if the argument could not be parsed
208      */

209     public abstract T parseValue(String string, ClassLoader classLoader) throws IllegalArgumentException;
210
211     /**
212      * Resolve this instance for serialization.
213      *
214      * @return the resolved object
215      * @throws java.io.ObjectStreamException if the object could not be resolved
216      */

217     protected final Object readResolve() throws ObjectStreamException {
218         try {
219             final Field field = declClass.getField(name);
220             final int modifiers = field.getModifiers();
221             if (! Modifier.isPublic(modifiers)) {
222                 throw new InvalidObjectException("Invalid Option instance (the field is not public)");
223             }
224             if (! Modifier.isStatic(modifiers)) {
225                 throw new InvalidObjectException("Invalid Option instance (the field is not static)");
226             }
227             final Option<?> option = (Option<?>) field.get(null);
228             if (option == null) {
229                 throw new InvalidObjectException("Invalid null Option");
230             }
231             return option;
232         } catch (NoSuchFieldException e) {
233             throw new InvalidObjectException("Invalid Option instance (no matching field)");
234         } catch (IllegalAccessException e) {
235             throw new InvalidObjectException("Invalid Option instance (Illegal access on field get)");
236         }
237     }
238
239     /**
240      * Create a builder for an immutable option set.
241      *
242      * @return the builder
243      */

244     public static Option.SetBuilder setBuilder() {
245         return new Option.SetBuilder();
246     }
247
248     /**
249      * A builder for an immutable option set.
250      */

251     public static class SetBuilder {
252         private List<Option<?>> optionSet = new ArrayList<Option<?>>();
253
254         SetBuilder() {
255         }
256
257         /**
258          * Add an option to this set.
259          *
260          * @param option the option to add
261          * @return this builder
262          */

263         public Option.SetBuilder add(Option<?> option) {
264             if (option == null) {
265                 throw msg.nullParameter("option");
266             }
267             optionSet.add(option);
268             return this;
269         }
270
271         /**
272          * Add options to this set.
273          *
274          * @param option1 the first option to add
275          * @param option2 the second option to add
276          * @return this builder
277          */

278         public Option.SetBuilder add(Option<?> option1, Option<?> option2) {
279             if (option1 == null) {
280                 throw msg.nullParameter("option1");
281             }
282             if (option2 == null) {
283                 throw msg.nullParameter("option2");
284             }
285             optionSet.add(option1);
286             optionSet.add(option2);
287             return this;
288         }
289
290         /**
291          * Add options to this set.
292          *
293          * @param option1 the first option to add
294          * @param option2 the second option to add
295          * @param option3 the third option to add
296          * @return this builder
297          */

298         public Option.SetBuilder add(Option<?> option1, Option<?> option2, Option<?> option3) {
299             if (option1 == null) {
300                 throw msg.nullParameter("option1");
301             }
302             if (option2 == null) {
303                 throw msg.nullParameter("option2");
304             }
305             if (option3 == null) {
306                 throw msg.nullParameter("option3");
307             }
308             optionSet.add(option1);
309             optionSet.add(option2);
310             optionSet.add(option3);
311             return this;
312         }
313
314         /**
315          * Add options to this set.
316          *
317          * @param options the options to add
318          * @return this builder
319          */

320         public Option.SetBuilder add(Option<?>... options) {
321             if (options == null) {
322                 throw msg.nullParameter("options");
323             }
324             for (Option<?> option : options) {
325                 add(option);
326             }
327             return this;
328         }
329
330         /**
331          * Add all options from a collection to this set.
332          *
333          * @param options the options to add
334          * @return this builder
335          */

336         public Option.SetBuilder addAll(Collection<Option<?>> options) {
337             if (options == null) {
338                 throw msg.nullParameter("option");
339             }
340             for (Option<?> option : options) {
341                 add(option);
342             }
343             return this;
344         }
345
346         /**
347          * Create the immutable option set instance.
348          *
349          * @return the option set
350          */

351         public Set<Option<?>> create() {
352             return Collections.unmodifiableSet(new LinkedHashSet<Option<?>>(optionSet));
353         }
354     }
355
356     interface ValueParser<T> {
357         T parseValue(String string, ClassLoader classLoader) throws IllegalArgumentException;
358     }
359
360     private static final Map<Class<?>, Option.ValueParser<?>> parsers;
361
362     private static final Option.ValueParser<?> noParser = new Option.ValueParser<Object>() {
363         public Object parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
364             throw msg.noOptionParser();
365         }
366     };
367
368     static {
369         final Map<Class<?>, Option.ValueParser<?>> map = new HashMap<Class<?>, Option.ValueParser<?>>();
370         map.put(Byte.classnew Option.ValueParser<Byte>() {
371             public Byte parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
372                 return Byte.decode(string.trim());
373             }
374         });
375         map.put(Short.classnew Option.ValueParser<Short>() {
376             public Short parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
377                 return Short.decode(string.trim());
378             }
379         });
380         map.put(Integer.classnew Option.ValueParser<Integer>() {
381             public Integer parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
382                 return Integer.decode(string.trim());
383             }
384         });
385         map.put(Long.classnew Option.ValueParser<Long>() {
386             public Long parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
387                 return Long.decode(string.trim());
388             }
389         });
390         map.put(String.classnew Option.ValueParser<String>() {
391             public String parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
392                 return string.trim();
393             }
394         });
395         map.put(Boolean.classnew Option.ValueParser<Boolean>() {
396             public Boolean parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
397                 return Boolean.valueOf(string.trim());
398             }
399         });
400         map.put(Property.classnew Option.ValueParser<Object>() {
401             public Object parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
402                 final int idx = string.indexOf('=');
403                 if (idx == -1) {
404                     throw msg.invalidOptionPropertyFormat(string);
405                 }
406                 return Property.of(string.substring(0, idx), string.substring(idx + 1, string.length()));
407             }
408         });
409         parsers = map;
410     }
411
412     static <T> Option.ValueParser<Class<? extends T>> getClassParser(final Class<T> argType) {
413         return new ValueParser<Class<? extends T>>() {
414             public Class<? extends T> parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
415                 try {
416                     return Class.forName(string, false, classLoader).asSubclass(argType);
417                 } catch (ClassNotFoundException e) {
418                     throw msg.classNotFound(string, e);
419                 } catch (ClassCastException e) {
420                     throw msg.classNotInstance(string, argType);
421                 }
422             }
423         };
424     }
425
426     static <T, E extends Enum<E>> Option.ValueParser<T> getEnumParser(final Class<T> enumType) {
427         return new ValueParser<T>() {
428             public T parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
429                 return enumType.cast(Enum.<E>valueOf(asEnum(enumType), string.trim()));
430             }
431         };
432     }
433
434     @SuppressWarnings("unchecked")
435     private static <T, E extends Enum<E>> Class<E> asEnum(final Class<T> enumType) {
436         return (Class<E>) enumType;
437     }
438
439     @SuppressWarnings("unchecked")
440     static <T> Option.ValueParser<T> getParser(final Class<T> argType) {
441         if (argType.isEnum()) {
442             return getEnumParser(argType);
443         } else {
444             final Option.ValueParser<?> value = parsers.get(argType);
445             return (Option.ValueParser<T>) (value == null ? noParser : value);
446         }
447     }
448 }
449
450