/*
* JBoss, Home of Professional Open Source
*
* Copyright 2009 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.util.
Iterator;
import java.util.
List;
import java.util.
ArrayList;
import java.util.
Map;
import java.util.
Collections;
import java.util.
IdentityHashMap;
import java.util.
Properties;
import java.io.
Serializable;
import static org.xnio._private.
Messages.
msg;
import static org.xnio._private.
Messages.
optionParseMsg;
/**
* An immutable map of options to option values. No {@code null} keys or values are permitted.
*/
public final class
OptionMap implements
Iterable<
Option<?>>,
Serializable {
private static final long
serialVersionUID = 3632842565346928132L;
private final
Map<
Option<?>,
Object>
value;
private
OptionMap(final
Map<
Option<?>,
Object>
value) {
this.
value =
value;
}
/**
* Determine whether this option map contains the given option.
*
* @param option the option to check
* @return {@code true} if the option is present in the option map
*/
public boolean
contains(
Option<?>
option) {
return
value.
containsKey(
option);
}
/**
* Get the value of an option from this option map.
*
* @param option the option to get
* @param <T> the type of the option
* @return the option value, or {@code null} if it is not present
*/
public <T> T
get(
Option<T>
option) {
return
option.
cast(
value.
get(
option));
}
/**
* Get the value of an option from this option map, with a specified default if the value is missing.
*
* @param option the option to get
* @param defaultValue the value to return if the option is not set
* @param <T> the type of the option
* @return the option value, or {@code null} if it is not present
*/
public <T> T
get(
Option<T>
option, T
defaultValue) {
final
Object o =
value.
get(
option);
return
o == null ?
defaultValue :
option.
cast(
o);
}
/**
* Get a boolean value from this option map, with a specified default if the value is missing.
*
* @param option the option to get
* @param defaultValue the default value if the option is not present
* @return the result
*/
public boolean
get(
Option<
Boolean>
option, boolean
defaultValue) {
final
Object o =
value.
get(
option);
return
o == null ?
defaultValue :
option.
cast(
o).
booleanValue();
}
/**
* Get a int value from this option map, with a specified default if the value is missing.
*
* @param option the option to get
* @param defaultValue the default value if the option is not present
* @return the result
*/
public int
get(
Option<
Integer>
option, int
defaultValue) {
final
Object o =
value.
get(
option);
return
o == null ?
defaultValue :
option.
cast(
o).
intValue();
}
/**
* Get a long value from this option map, with a specified default if the value is missing.
*
* @param option the option to get
* @param defaultValue the default value if the option is not present
* @return the result
*/
public long
get(
Option<
Long>
option, long
defaultValue) {
final
Object o =
value.
get(
option);
return
o == null ?
defaultValue :
option.
cast(
o).
longValue();
}
/**
* Iterate over the options in this map.
*
* @return an iterator over the options
*/
public
Iterator<
Option<?>>
iterator() {
return
Collections.
unmodifiableCollection(
value.
keySet()).
iterator();
}
/**
* Get the number of options stored in this map.
*
* @return the number of options
*/
public int
size() {
return
value.
size();
}
/**
* The empty option map.
*/
public static final
OptionMap EMPTY = new
OptionMap(
Collections.<
Option<?>,
Object>
emptyMap());
/**
* Create a new builder.
*
* @return a new builder
*/
public static
Builder builder() {
return new
Builder();
}
/**
* Create a single-valued option map.
*
* @param option the option to put in the map
* @param value the option value
* @param <T> the option value type
* @return the option map
*
* @since 3.0
*/
public static <T>
OptionMap create(
Option<T>
option, T
value) {
if (
option == null) {
throw
msg.
nullParameter("option");
}
if (
value == null) {
throw
msg.
nullParameter("value");
}
return new
OptionMap(
Collections.<
Option<?>,
Object>
singletonMap(
option,
option.
cast(
value)));
}
/**
* Create a two-valued option map. If both options are the same key, then only the second one is added
* to the map.
*
* @param option1 the first option to put in the map
* @param value1 the first option value
* @param option2 the second option to put in the map
* @param value2 the second option value
* @param <T1> the first option value type
* @param <T2> the second option value type
* @return the option map
*
* @since 3.0
*/
public static <T1, T2>
OptionMap create(
Option<T1>
option1, T1
value1,
Option<T2>
option2, T2
value2) {
if (
option1 == null) {
throw
msg.
nullParameter("option1");
}
if (
value1 == null) {
throw
msg.
nullParameter("value1");
}
if (
option2 == null) {
throw
msg.
nullParameter("option2");
}
if (
value2 == null) {
throw
msg.
nullParameter("value2");
}
if (
option1 ==
option2) {
return
create(
option2,
value2);
}
final
IdentityHashMap<
Option<?>,
Object>
map = new
IdentityHashMap<
Option<?>,
Object>(2);
map.
put(
option1,
value1);
map.
put(
option2,
value2);
return new
OptionMap(
map);
}
public
String toString() {
final
StringBuilder builder = new
StringBuilder();
builder.
append('{');
final
Iterator<
Map.
Entry<
Option<?>,
Object>>
iterator =
value.
entrySet().
iterator();
while (
iterator.
hasNext()) {
final
Map.
Entry<
Option<?>,
Object>
entry =
iterator.
next();
builder.
append(
entry.
getKey()).
append("=>").
append(
entry.
getValue());
if (
iterator.
hasNext()) {
builder.
append(',');
}
}
builder.
append('}');
return
builder.
toString();
}
/**
* Determine whether this option map is equal to another.
*
* @param other the other option map
* @return {@code true} if they are equal, {@code false} otherwise
*/
public boolean
equals(
Object other) {
return
other instanceof
OptionMap &&
equals((
OptionMap)
other);
}
/**
* Determine whether this option map is equal to another.
*
* @param other the other option map
* @return {@code true} if they are equal, {@code false} otherwise
*/
public boolean
equals(
OptionMap other) {
return this ==
other ||
other != null &&
value.
equals(
other.
value);
}
/**
* Get the hash code for this option map.
*
* @return the hash code
*/
public int
hashCode() {
return
value.
hashCode();
}
/**
* A builder for immutable option maps. Create an instance with the {@link OptionMap#builder()} method.
*/
public static final class
Builder {
private
Builder() {
}
private static class
OVPair<T> {
Option<T>
option;
T
value;
private
OVPair(final
Option<T>
option, final T
value) {
this.
option =
option;
this.
value =
value;
}
}
private
List<
OVPair<?>>
list = new
ArrayList<
OVPair<?>>();
/**
* Set a key-value pair, parsing the value from the given string.
*
* @param key the key
* @param stringValue the string value
* @param <T> the option type
* @return this builder
*/
public <T>
Builder parse(
Option<T>
key,
String stringValue) {
set(
key,
key.
parseValue(
stringValue,
key.
getClass().
getClassLoader()));
return this;
}
/**
* Set a key-value pair, parsing the value from the given string.
*
* @param key the key
* @param stringValue the string value
* @param classLoader the class loader to use for parsing the value
* @param <T> the option type
* @return this builder
*/
public <T>
Builder parse(
Option<T>
key,
String stringValue,
ClassLoader classLoader) {
set(
key,
key.
parseValue(
stringValue,
classLoader));
return this;
}
/**
* Add all options from a properties file. Finds all entries which start with a given prefix followed by '.';
* the remainder of the property key (after the prefix) is the option name, and the value is the option value.
* <p>If the prefix does not end with '.' character, a '.' will be appended to it before parsing.
*
* @param props the properties to read
* @param prefix the prefix
* @param optionClassLoader the class loader to use to resolve option names
* @return this builder
*/
public
Builder parseAll(
Properties props,
String prefix,
ClassLoader optionClassLoader) {
if (!
prefix.
endsWith(".")) {
prefix =
prefix + ".";
}
for (
String name :
props.
stringPropertyNames()) {
if (
name.
startsWith(
prefix)) {
final
String optionName =
name.
substring(
prefix.
length());
try {
final
Option<?>
option =
Option.
fromString(
optionName,
optionClassLoader);
parse(
option,
props.
getProperty(
name),
optionClassLoader);
} catch (
IllegalArgumentException e) {
optionParseMsg.
invalidOptionInProperty(
optionName,
name,
e);
}
}
}
return this;
}
/**
* Add all options from a properties file. Finds all entries which start with a given prefix followed by '.';
* the remainder of the property key (after the prefix) is the option name, and the value is the option value.
*<p>If the prefix does not end with '.' character, a '.' will be appended to it before parsing.
*
* @param props the properties to read
* @param prefix the prefix
* @return this builder
*/
public
Builder parseAll(
Properties props,
String prefix) {
if (!
prefix.
endsWith(".")) {
prefix =
prefix + ".";
}
for (
String name :
props.
stringPropertyNames()) {
if (
name.
startsWith(
prefix)) {
final
String optionName =
name.
substring(
prefix.
length());
try {
final
Option<?>
option =
Option.
fromString(
optionName,
getClass().
getClassLoader());
parse(
option,
props.
getProperty(
name));
} catch (
IllegalArgumentException e) {
optionParseMsg.
invalidOptionInProperty(
optionName,
name,
e);
}
}
}
return this;
}
/**
* Set a key-value pair.
*
* @param key the key
* @param value the value
* @param <T> the option type
* @return this builder
*/
public <T>
Builder set(
Option<T>
key, T
value) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
if (
value == null) {
throw
msg.
nullParameter("value");
}
list.
add(new
OVPair<T>(
key,
value));
return this;
}
/**
* Set an int value for an Integer key.
*
* @param key the option
* @param value the value
* @return this builder
*/
public
Builder set(
Option<
Integer>
key, int
value) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
list.
add(new
OVPair<
Integer>(
key,
Integer.
valueOf(
value)));
return this;
}
/**
* Set int values for an Integer sequence key.
*
* @param key the key
* @param values the values
* @return this builder
*/
public
Builder setSequence(
Option<
Sequence<
Integer>>
key, int...
values) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
Integer[]
a = new
Integer[
values.length];
for (int
i = 0;
i <
values.length;
i++) {
a[
i] =
Integer.
valueOf(
values[
i]);
}
list.
add(new
OVPair<
Sequence<
Integer>>(
key,
Sequence.
of(
a)));
return this;
}
/**
* Set a long value for a Long key.
*
* @param key the option
* @param value the value
* @return this builder
*/
public
Builder set(
Option<
Long>
key, long
value) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
list.
add(new
OVPair<
Long>(
key,
Long.
valueOf(
value)));
return this;
}
/**
* Set long values for a Long sequence key.
*
* @param key the key
* @param values the values
* @return this builder
*/
public
Builder setSequence(
Option<
Sequence<
Long>>
key, long...
values) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
Long[]
a = new
Long[
values.length];
for (int
i = 0;
i <
values.length;
i++) {
a[
i] =
Long.
valueOf(
values[
i]);
}
list.
add(new
OVPair<
Sequence<
Long>>(
key,
Sequence.
of(
a)));
return this;
}
/**
* Set a boolean value for a Boolean key.
*
* @param key the option
* @param value the value
* @return this builder
*/
public
Builder set(
Option<
Boolean>
key, boolean
value) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
list.
add(new
OVPair<
Boolean>(
key,
Boolean.
valueOf(
value)));
return this;
}
/**
* Set boolean values for an Boolean sequence key.
*
* @param key the key
* @param values the values
* @return this builder
*/
public
Builder setSequence(
Option<
Sequence<
Boolean>>
key, boolean...
values) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
Boolean[]
a = new
Boolean[
values.length];
for (int
i = 0;
i <
values.length;
i++) {
a[
i] =
Boolean.
valueOf(
values[
i]);
}
list.
add(new
OVPair<
Sequence<
Boolean>>(
key,
Sequence.
of(
a)));
return this;
}
/**
* Set a key-value pair, where the value is a sequence type.
*
* @param key the key
* @param values the values
* @param <T> the option type
* @return this builder
*/
public <T>
Builder setSequence(
Option<
Sequence<T>>
key, T...
values) {
if (
key == null) {
throw
msg.
nullParameter("key");
}
list.
add(new
OVPair<
Sequence<T>>(
key,
Sequence.
of(
values)));
return this;
}
private <T> void
copy(
Map<?, ?>
map,
Option<T>
option) {
set(
option,
option.
cast(
map.
get(
option)));
}
/**
* Add all the entries of a map. Any keys of the map which are not valid {@link Option}s, or whose
* values are not valid arguments for the given {@code Option}, will cause an exception to be thrown.
* Any keys which occur more than once in this builder will be overwritten with the last occurring value.
*
* @param map the map
* @return this builder
* @throws ClassCastException if any entries of the map are not valid option-value pairs
*/
public
Builder add(
Map<?, ?>
map) throws
ClassCastException {
for (
Object key :
map.
keySet()) {
final
Option<?>
option =
Option.class.
cast(
key);
copy(
map,
option);
}
return this;
}
private <T> void
copy(
OptionMap optionMap,
Option<T>
option) {
set(
option,
optionMap.
get(
option));
}
/**
* Add all entries from an existing option map to the one being built.
* Any keys which occur more than once in this builder will be overwritten with the last occurring value.
*
* @param optionMap the original option map
* @return this builder
*/
public
Builder addAll(
OptionMap optionMap) {
for (
Option<?>
option :
optionMap) {
copy(
optionMap,
option);
}
return this;
}
/**
* Build a map that reflects the current state of this builder.
*
* @return the new immutable option map
*/
public
OptionMap getMap() {
final
List<
OVPair<?>>
list = this.
list;
if (
list.
size() == 0) {
return
EMPTY;
} else if (
list.
size() == 1) {
final
OVPair<?>
pair =
list.
get(0);
return new
OptionMap(
Collections.<
Option<?>,
Object>
singletonMap(
pair.
option,
pair.
value));
} else {
final
Map<
Option<?>,
Object>
map = new
IdentityHashMap<
Option<?>,
Object>();
for (
OVPair<?>
ovPair :
list) {
map.
put(
ovPair.
option,
ovPair.
value);
}
return new
OptionMap(
map);
}
}
}
}