/*
* JBoss, Home of Professional Open Source
*
* Copyright 2010 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xnio;
import java.io.
InvalidObjectException;
import java.io.
ObjectStreamException;
import java.io.
Serializable;
import java.lang.reflect.
Field;
import java.lang.reflect.
Modifier;
import java.util.
ArrayList;
import java.util.
Collection;
import java.util.
Collections;
import java.util.
HashMap;
import java.util.
LinkedHashSet;
import java.util.
List;
import java.util.
Map;
import java.util.
Set;
import static org.xnio._private.
Messages.
msg;
/**
* A strongly-typed option to configure an aspect of a service or connection. Options are immutable and use identity comparisons
* and hash codes. Options should always be declared as <code>public static final</code> members in order to support serialization.
*
* @param <T> the option value type
*/
public abstract class
Option<T> implements
Serializable {
private static final long
serialVersionUID = -1564427329140182760L;
private final
Class<?>
declClass;
private final
String name;
Option(final
Class<?>
declClass, final
String name) {
if (
declClass == null) {
throw
msg.
nullParameter("declClass");
}
if (
name == null) {
throw
msg.
nullParameter("name");
}
this.
declClass =
declClass;
this.
name =
name;
}
/**
* Create an option with a simple type. The class object given <b>must</b> represent some immutable type, otherwise
* unexpected behavior may result.
*
* @param declClass the declaring class of the option
* @param name the (field) name of this option
* @param type the class of the value associated with this option
* @return the option instance
*/
public static <T>
Option<T>
simple(final
Class<?>
declClass, final
String name, final
Class<T>
type) {
return new
SingleOption<T>(
declClass,
name,
type);
}
/**
* Create an option with a sequence type. The class object given <b>must</b> represent some immutable type, otherwise
* unexpected behavior may result.
*
* @param declClass the declaring class of the option
* @param name the (field) name of this option
* @param elementType the class of the sequence element value associated with this option
* @return the option instance
*/
public static <T>
Option<
Sequence<T>>
sequence(final
Class<?>
declClass, final
String name, final
Class<T>
elementType) {
return new
SequenceOption<T>(
declClass,
name,
elementType);
}
/**
* Create an option with a class type. The class object given may represent any type.
*
* @param declClass the declaring class of the option
* @param name the (field) name of this option
* @param declType the class object for the type of the class object given
* @param <T> the type of the class object given
* @return the option instance
*/
public static <T>
Option<
Class<? extends T>>
type(final
Class<?>
declClass, final
String name, final
Class<T>
declType) {
return new
TypeOption<T>(
declClass,
name,
declType);
}
/**
* Create an option with a sequence-of-types type. The class object given may represent any type.
*
* @param declClass the declaring class of the option
* @param name the (field) name of this option
* @param elementDeclType the class object for the type of the sequence element class object given
* @param <T> the type of the sequence element class object given
* @return the option instance
*/
public static <T>
Option<
Sequence<
Class<? extends T>>>
typeSequence(final
Class<?>
declClass, final
String name, final
Class<T>
elementDeclType) {
return new
TypeSequenceOption<T>(
declClass,
name,
elementDeclType);
}
/**
* Get the name of this option.
*
* @return the option name
*/
public
String getName() {
return
name;
}
/**
* Get a human-readable string representation of this object.
*
* @return the string representation
*/
public
String toString() {
return
declClass.
getName() + "." +
name;
}
/**
* Get an option from a string name, using the given classloader. If the classloader is {@code null}, the bootstrap
* classloader will be used.
*
* @param name the option string
* @param classLoader the class loader
* @return the option
* @throws IllegalArgumentException if the given option name is not valid
*/
public static
Option<?>
fromString(
String name,
ClassLoader classLoader) throws
IllegalArgumentException {
final int
lastDot =
name.
lastIndexOf('.');
if (
lastDot == -1) {
throw
msg.
invalidOptionName(
name);
}
final
String fieldName =
name.
substring(
lastDot + 1);
final
String className =
name.
substring(0,
lastDot);
final
Class<?>
clazz;
try {
clazz =
Class.
forName(
className, true,
classLoader);
} catch (
ClassNotFoundException e) {
throw
msg.
optionClassNotFound(
className,
classLoader);
}
final
Field field;
try {
field =
clazz.
getField(
fieldName);
} catch (
NoSuchFieldException e) {
throw
msg.
noField(
fieldName,
clazz);
}
final int
modifiers =
field.
getModifiers();
if (!
Modifier.
isPublic(
modifiers)) {
throw
msg.
fieldNotAccessible(
fieldName,
clazz);
}
if (!
Modifier.
isStatic(
modifiers)) {
throw
msg.
fieldNotStatic(
fieldName,
clazz);
}
final
Option<?>
option;
try {
option = (
Option<?>)
field.
get(null);
} catch (
IllegalAccessException e) {
throw
msg.
fieldNotAccessible(
fieldName,
clazz);
}
if (
option == null) {
throw
msg.
invalidNullOption(
name);
}
return
option;
}
/**
* Return the given object as the type of this option. If the cast could not be completed, an exception is thrown.
*
* @param o the object to cast
* @return the cast object
* @throws ClassCastException if the object is not of a compatible type
*/
public abstract T
cast(
Object o) throws
ClassCastException;
/**
* Return the given object as the type of this option. If the cast could not be completed, an exception is thrown.
*
* @param o the object to cast
* @param defaultVal the value to return if {@code o} is {@code null}
*
* @return the cast object
*
* @throws ClassCastException if the object is not of a compatible type
*/
public final T
cast(
Object o, T
defaultVal) throws
ClassCastException {
return
o == null ?
defaultVal :
cast(
o);
}
/**
* Parse a string value for this option.
*
* @param string the string
* @param classLoader the class loader to use to parse the value
* @return the parsed value
* @throws IllegalArgumentException if the argument could not be parsed
*/
public abstract T
parseValue(
String string,
ClassLoader classLoader) throws
IllegalArgumentException;
/**
* Resolve this instance for serialization.
*
* @return the resolved object
* @throws java.io.ObjectStreamException if the object could not be resolved
*/
protected final
Object readResolve() throws
ObjectStreamException {
try {
final
Field field =
declClass.
getField(
name);
final int
modifiers =
field.
getModifiers();
if (!
Modifier.
isPublic(
modifiers)) {
throw new
InvalidObjectException("Invalid Option instance (the field is not public)");
}
if (!
Modifier.
isStatic(
modifiers)) {
throw new
InvalidObjectException("Invalid Option instance (the field is not static)");
}
final
Option<?>
option = (
Option<?>)
field.
get(null);
if (
option == null) {
throw new
InvalidObjectException("Invalid null Option");
}
return
option;
} catch (
NoSuchFieldException e) {
throw new
InvalidObjectException("Invalid Option instance (no matching field)");
} catch (
IllegalAccessException e) {
throw new
InvalidObjectException("Invalid Option instance (Illegal access on field get)");
}
}
/**
* Create a builder for an immutable option set.
*
* @return the builder
*/
public static
Option.
SetBuilder setBuilder() {
return new
Option.
SetBuilder();
}
/**
* A builder for an immutable option set.
*/
public static class
SetBuilder {
private
List<
Option<?>>
optionSet = new
ArrayList<
Option<?>>();
SetBuilder() {
}
/**
* Add an option to this set.
*
* @param option the option to add
* @return this builder
*/
public
Option.
SetBuilder add(
Option<?>
option) {
if (
option == null) {
throw
msg.
nullParameter("option");
}
optionSet.
add(
option);
return this;
}
/**
* Add options to this set.
*
* @param option1 the first option to add
* @param option2 the second option to add
* @return this builder
*/
public
Option.
SetBuilder add(
Option<?>
option1,
Option<?>
option2) {
if (
option1 == null) {
throw
msg.
nullParameter("option1");
}
if (
option2 == null) {
throw
msg.
nullParameter("option2");
}
optionSet.
add(
option1);
optionSet.
add(
option2);
return this;
}
/**
* Add options to this set.
*
* @param option1 the first option to add
* @param option2 the second option to add
* @param option3 the third option to add
* @return this builder
*/
public
Option.
SetBuilder add(
Option<?>
option1,
Option<?>
option2,
Option<?>
option3) {
if (
option1 == null) {
throw
msg.
nullParameter("option1");
}
if (
option2 == null) {
throw
msg.
nullParameter("option2");
}
if (
option3 == null) {
throw
msg.
nullParameter("option3");
}
optionSet.
add(
option1);
optionSet.
add(
option2);
optionSet.
add(
option3);
return this;
}
/**
* Add options to this set.
*
* @param options the options to add
* @return this builder
*/
public
Option.
SetBuilder add(
Option<?>...
options) {
if (
options == null) {
throw
msg.
nullParameter("options");
}
for (
Option<?>
option :
options) {
add(
option);
}
return this;
}
/**
* Add all options from a collection to this set.
*
* @param options the options to add
* @return this builder
*/
public
Option.
SetBuilder addAll(
Collection<
Option<?>>
options) {
if (
options == null) {
throw
msg.
nullParameter("option");
}
for (
Option<?>
option :
options) {
add(
option);
}
return this;
}
/**
* Create the immutable option set instance.
*
* @return the option set
*/
public
Set<
Option<?>>
create() {
return
Collections.
unmodifiableSet(new
LinkedHashSet<
Option<?>>(
optionSet));
}
}
interface
ValueParser<T> {
T
parseValue(
String string,
ClassLoader classLoader) throws
IllegalArgumentException;
}
private static final
Map<
Class<?>,
Option.
ValueParser<?>>
parsers;
private static final
Option.
ValueParser<?>
noParser = new
Option.
ValueParser<
Object>() {
public
Object parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
throw
msg.
noOptionParser();
}
};
static {
final
Map<
Class<?>,
Option.
ValueParser<?>>
map = new
HashMap<
Class<?>,
Option.
ValueParser<?>>();
map.
put(
Byte.class, new
Option.
ValueParser<
Byte>() {
public
Byte parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
Byte.
decode(
string.
trim());
}
});
map.
put(
Short.class, new
Option.
ValueParser<
Short>() {
public
Short parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
Short.
decode(
string.
trim());
}
});
map.
put(
Integer.class, new
Option.
ValueParser<
Integer>() {
public
Integer parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
Integer.
decode(
string.
trim());
}
});
map.
put(
Long.class, new
Option.
ValueParser<
Long>() {
public
Long parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
Long.
decode(
string.
trim());
}
});
map.
put(
String.class, new
Option.
ValueParser<
String>() {
public
String parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
string.
trim();
}
});
map.
put(
Boolean.class, new
Option.
ValueParser<
Boolean>() {
public
Boolean parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
Boolean.
valueOf(
string.
trim());
}
});
map.
put(
Property.class, new
Option.
ValueParser<
Object>() {
public
Object parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
final int
idx =
string.
indexOf('=');
if (
idx == -1) {
throw
msg.
invalidOptionPropertyFormat(
string);
}
return
Property.
of(
string.
substring(0,
idx),
string.
substring(
idx + 1,
string.
length()));
}
});
parsers =
map;
}
static <T>
Option.
ValueParser<
Class<? extends T>>
getClassParser(final
Class<T>
argType) {
return new
ValueParser<
Class<? extends T>>() {
public
Class<? extends T>
parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
try {
return
Class.
forName(
string, false,
classLoader).
asSubclass(
argType);
} catch (
ClassNotFoundException e) {
throw
msg.
classNotFound(
string,
e);
} catch (
ClassCastException e) {
throw
msg.
classNotInstance(
string,
argType);
}
}
};
}
static <T>
Option.
ValueParser<T>
getEnumParser(final
Class<T>
enumType) {
return new
ValueParser<T>() {
@
SuppressWarnings("unchecked")
public T
parseValue(final
String string, final
ClassLoader classLoader) throws
IllegalArgumentException {
return
enumType.
cast(
Enum.
valueOf(
enumType.
asSubclass(
Enum.class),
string.
trim()));
}
};
}
@
SuppressWarnings("unchecked")
static <T>
Option.
ValueParser<T>
getParser(final
Class<T>
argType) {
if (
argType.
isEnum()) {
return
getEnumParser(
argType);
} else {
final
Option.
ValueParser<?>
value =
parsers.
get(
argType);
return (
Option.
ValueParser<T>) (
value == null ?
noParser :
value);
}
}
}