support typed option (#27)

Change-Id: I845d3e0f59b3de4eb086a57f206a02fae0aae549
This commit is contained in:
Jermy Li 2019-06-17 15:13:07 +08:00 committed by zhoney
parent 64bd1543e8
commit 2f453c0af9
12 changed files with 356 additions and 157 deletions

View File

@ -6,7 +6,7 @@
<groupId>com.baidu.hugegraph</groupId>
<artifactId>hugegraph-common</artifactId>
<version>1.6.4</version>
<version>1.6.5</version>
<name>hugegraph-common</name>
<url>https://github.com/hugegraph/hugegraph-common</url>
@ -212,7 +212,7 @@
<manifestEntries>
<!-- Must be on one line, otherwise the automatic
upgrade script cannot replace the version number -->
<Implementation-Version>1.6.4.0</Implementation-Version>
<Implementation-Version>1.6.5.0</Implementation-Version>
</manifestEntries>
</archive>
</configuration>

View File

@ -0,0 +1,47 @@
/*
* Copyright 2017 HugeGraph Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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 com.baidu.hugegraph.config;
import java.util.function.Function;
import com.google.common.base.Predicate;
public class ConfigConvOption<R> extends TypedOption<String, R> {
private final Function<String, R> converter;
public ConfigConvOption(String name, String desc, Predicate<String> pred,
Function<String, R> convert, String value) {
this(name, false, desc, pred, convert, value);
}
public ConfigConvOption(String name, boolean required, String desc,
Predicate<String> pred, Function<String, R> convert,
String value) {
super(name, required, desc, pred, String.class, value);
this.converter = convert;
}
@Override
public R convert(Object value) {
assert value instanceof String;
return this.converter.apply((String) value);
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2017 HugeGraph Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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 com.baidu.hugegraph.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import com.baidu.hugegraph.util.E;
import com.google.common.base.Predicate;
public class ConfigListConvOption<T, R> extends TypedOption<List<T>, List<R>> {
private final Class<T> elemClass;
private final Function<T, R> converter;
@SuppressWarnings("unchecked")
public ConfigListConvOption(String name, String desc,
Predicate<List<T>> pred, Function<T, R> convert,
T value) {
this(name, desc, pred, convert, (Class<T>) value.getClass(), value);
}
@SuppressWarnings("unchecked")
public ConfigListConvOption(String name, String desc,
Predicate<List<T>> pred, Function<T, R> convert,
Class<T> clazz, T... values) {
this(name, false, desc, pred, convert, clazz, Arrays.asList(values));
}
@SuppressWarnings("unchecked")
public ConfigListConvOption(String name, boolean required, String desc,
Predicate<List<T>> pred, Function<T, R> convert,
Class<T> clazz, List<T> values) {
super(name, required, desc, pred,
(Class<List<T>>) values.getClass(), values);
E.checkArgumentNotNull(clazz, "Element class can't be null");
this.elemClass = clazz;
this.converter = convert;
}
@Override
public List<R> convert(Object value) {
List<T> results = ConfigListOption.convert(value, part -> {
return super.convert(part, this.elemClass);
});
List<R> enums = new ArrayList<>(results.size());
for (T elem : results) {
enums.add(this.converter.apply(elem));
}
return enums;
}
}

View File

@ -22,6 +22,7 @@ package com.baidu.hugegraph.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import com.baidu.hugegraph.util.E;
import com.google.common.base.Predicate;
@ -32,30 +33,37 @@ public class ConfigListOption<T> extends ConfigOption<List<T>> {
@SuppressWarnings("unchecked")
public ConfigListOption(String name, String desc,
Predicate<List<T>> func, T value) {
this(name, false, desc, func, (Class<T>) value.getClass(), value);
Predicate<List<T>> pred, T value) {
this(name, desc, pred, (Class<T>) value.getClass(), value);
}
@SuppressWarnings("unchecked")
public ConfigListOption(String name, boolean required, String desc,
Predicate<List<T>> func, Class<T> clazz,
public ConfigListOption(String name, String desc,
Predicate<List<T>> pred, Class<T> clazz,
T... values) {
this(name, required, desc, func, clazz, Arrays.asList(values));
this(name, false, desc, pred, clazz, Arrays.asList(values));
}
@SuppressWarnings("unchecked")
public ConfigListOption(String name, boolean required, String desc,
Predicate<List<T>> func, Class<T> clazz,
Predicate<List<T>> pred, Class<T> clazz,
List<T> values) {
super(name, required, desc, func,
super(name, required, desc, pred,
(Class<List<T>>) values.getClass(), values);
E.checkArgumentNotNull(clazz, "Element class can't be null");
this.elemClass = clazz;
}
@Override
@SuppressWarnings("unchecked")
public List<T> convert(Object value) {
return convert(value, part -> super.convert(part, this.elemClass));
}
@SuppressWarnings("unchecked")
public static <T> List<T> convert(Object value, Function<String, ?> conv) {
if (value instanceof List) {
return (List<T>) value;
}
// If target data type is List, parse it as a list
String str = (String) value;
if (str.startsWith("[") && str.endsWith("]")) {
@ -69,7 +77,7 @@ public class ConfigListOption<T> extends ConfigOption<List<T>> {
String[] parts = str.split(",");
List<T> results = new ArrayList<>(parts.length);
for (String part : parts) {
results.add((T) super.convert(part.trim(), this.elemClass));
results.add((T) conv.apply(part.trim()));
}
return results;
}

View File

@ -19,151 +19,21 @@
package com.baidu.hugegraph.config;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import org.apache.commons.configuration.PropertyConverter;
import org.slf4j.Logger;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.Log;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
public class ConfigOption<T> {
private static final Logger LOG = Log.logger(ConfigOption.class);
private static final Set<Class<?>> ACCEPTED_DATA_TYPES;
private static final String ACCEPTED_DATA_TYPES_STRING;
static {
ACCEPTED_DATA_TYPES = ImmutableSet.of(
Boolean.class,
Short.class,
Integer.class,
Byte.class,
Long.class,
Float.class,
Double.class,
String.class,
String[].class,
List.class
);
ACCEPTED_DATA_TYPES_STRING = Joiner.on(", ").join(ACCEPTED_DATA_TYPES);
}
private final String name;
private final String desc;
private final boolean required;
private final Class<T> dataType;
private final T defaultValue;
private final Predicate<T> checkFunc;
public class ConfigOption<T> extends TypedOption<T, T> {
public ConfigOption(String name, String desc, T value) {
this(name, desc, null, value);
}
@SuppressWarnings("unchecked")
public ConfigOption(String name, String desc, Predicate<T> func, T value) {
this(name, false, desc, func, (Class<T>) value.getClass(), value);
public ConfigOption(String name, String desc, Predicate<T> pred, T value) {
this(name, false, desc, pred, (Class<T>) value.getClass(), value);
}
@SuppressWarnings("unchecked")
public ConfigOption(String name, boolean required, String desc,
Predicate<T> func, Class<T> type, T value) {
E.checkNotNull(name, "name");
E.checkNotNull(type, "dataType");
this.name = name;
this.dataType = (Class<T>) this.checkAndAssignDataType(type);
this.defaultValue = value;
this.required = required;
this.desc = desc;
this.checkFunc = func;
this.check(this.defaultValue);
}
private Class<?> checkAndAssignDataType(Class<T> dataType) {
for (Class<?> clazz : ACCEPTED_DATA_TYPES) {
if (clazz.isAssignableFrom(dataType)) {
return clazz;
}
}
String msg = String.format("Input data type '%s' doesn't belong " +
"to acceptable type set: [%s]",
dataType, ACCEPTED_DATA_TYPES_STRING);
throw new IllegalArgumentException(msg);
}
public String name() {
return this.name;
}
public Class<T> dataType() {
return this.dataType;
}
public String desc() {
return this.desc;
}
public boolean required() {
return this.required;
}
public T defaultValue() {
return this.defaultValue;
}
@SuppressWarnings("unchecked")
public T convert(Object value) {
return (T) this.convert(value, this.dataType);
}
public Object convert(Object value, Class<?> dataType) {
if (dataType.equals(String.class)) {
return value;
}
// Use PropertyConverter method `toXXX` convert value
String methodTo = "to" + dataType.getSimpleName();
try {
Method method = PropertyConverter.class.getMethod(
methodTo, Object.class);
return method.invoke(null, value);
} catch (ReflectiveOperationException e) {
LOG.error("Invalid type of value '{}' for option '{}'",
value, this.name, e);
throw new ConfigException(
"Invalid type of value '%s' for option '%s', " +
"expect '%s' type",
value, this.name, dataType.getSimpleName());
}
}
public void check(Object value) {
E.checkNotNull(value, "value", this.name);
E.checkArgument(this.dataType.isInstance(value),
"Invalid type of value '%s' for option '%s'",
value, this.name);
if (this.checkFunc != null) {
@SuppressWarnings("unchecked")
T result = (T) value;
E.checkArgument(this.checkFunc.apply(result),
"Invalid option value for '%s': %s",
this.name, value);
}
}
@Override
public String toString() {
return String.format("[%s]%s=%s", this.dataType.getSimpleName(),
this.name, this.defaultValue);
Predicate<T> pred, Class<T> type, T value) {
super(name, required, desc, pred, type, value);
}
}

View File

@ -110,9 +110,9 @@ public class HugeConfig extends PropertiesConfiguration {
}
@SuppressWarnings("unchecked")
public <T> T get(ConfigOption<T> option) {
public <T, R> R get(TypedOption<T, R> option) {
Object value = this.getProperty(option.name());
return value != null ? (T) value : option.defaultValue();
return value != null ? (R) value : option.defaultValue();
}
public Map<String, String> getMap(ConfigListOption<String> option) {
@ -144,7 +144,7 @@ public class HugeConfig extends PropertiesConfiguration {
E.checkArgument(value instanceof String,
"Invalid value for key '%s': %s", key, value);
ConfigOption<?> option = OptionSpace.get(key);
TypedOption<?, ?> option = OptionSpace.get(key);
Class<?> dataType = option.dataType();
if (List.class.isAssignableFrom(dataType)) {

View File

@ -21,6 +21,7 @@ package com.baidu.hugegraph.config;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
@ -62,6 +63,16 @@ public final class OptionChecker {
};
}
@SuppressWarnings("unchecked")
public static <O> Predicate<List<O>> inValues(O... values) {
return new Predicate<List<O>>() {
@Override
public boolean apply(@Nullable List<O> o) {
return o != null && Arrays.asList(values).containsAll(o);
}
};
}
public static <N extends Number> Predicate<N> positiveInt() {
return new Predicate<N>() {
@Override

View File

@ -32,7 +32,7 @@ public class OptionHolder {
private static final Logger LOG = Log.logger(HugeConfig.class);
protected Map<String, ConfigOption<?>> options;
protected Map<String, TypedOption<?, ?>> options;
public OptionHolder() {
this.options = new HashMap<>();
@ -41,7 +41,7 @@ public class OptionHolder {
protected void registerOptions() {
for (Field field : this.getClass().getFields()) {
try {
ConfigOption<?> option = (ConfigOption<?>) field.get(this);
TypedOption<?, ?> option = (TypedOption<?, ?>) field.get(this);
// Fields of subclass first, don't overwrite by superclass
this.options.putIfAbsent(option.name(), option);
} catch (Exception e) {
@ -52,7 +52,7 @@ public class OptionHolder {
}
}
public Map<String, ConfigOption<?>> options() {
public Map<String, TypedOption<?, ?>> options() {
return Collections.unmodifiableMap(this.options);
}
}

View File

@ -34,7 +34,7 @@ public final class OptionSpace {
private static final Logger LOG = Log.logger(OptionSpace.class);
private static final Map<String, Class<? extends OptionHolder>> holders;
private static final Map<String, ConfigOption<?>> options;
private static final Map<String, TypedOption<?, ?>> options;
private static final String INSTANCE_METHOD = "instance";
static {
@ -105,7 +105,7 @@ public final class OptionSpace {
return options.containsKey(key);
}
public static ConfigOption<?> get(String key) {
public static TypedOption<?, ?> get(String key) {
return options.get(key);
}
}

View File

@ -0,0 +1,160 @@
/*
* Copyright 2017 HugeGraph Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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 com.baidu.hugegraph.config;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import org.apache.commons.configuration.PropertyConverter;
import org.slf4j.Logger;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.Log;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
public class TypedOption<T, R> {
private static final Logger LOG = Log.logger(TypedOption.class);
private static final Set<Class<?>> ACCEPTED_DATA_TYPES;
private static final String ACCEPTED_DATA_TYPES_STRING;
static {
ACCEPTED_DATA_TYPES = ImmutableSet.of(
Boolean.class,
Short.class,
Integer.class,
Byte.class,
Long.class,
Float.class,
Double.class,
String.class,
String[].class,
List.class
);
ACCEPTED_DATA_TYPES_STRING = Joiner.on(", ").join(ACCEPTED_DATA_TYPES);
}
private final String name;
private final String desc;
private final boolean required;
private final Class<T> dataType;
private final T defaultValue;
private final Predicate<T> checkFunc;
@SuppressWarnings("unchecked")
public TypedOption(String name, boolean required, String desc,
Predicate<T> pred, Class<T> type, T value) {
E.checkNotNull(name, "name");
E.checkNotNull(type, "dataType");
this.name = name;
this.dataType = (Class<T>) this.checkAndAssignDataType(type);
this.defaultValue = value;
this.required = required;
this.desc = desc;
this.checkFunc = pred;
this.check(this.defaultValue);
}
private Class<?> checkAndAssignDataType(Class<T> dataType) {
for (Class<?> clazz : ACCEPTED_DATA_TYPES) {
if (clazz.isAssignableFrom(dataType)) {
return clazz;
}
}
String msg = String.format("Input data type '%s' doesn't belong " +
"to acceptable type set: [%s]",
dataType, ACCEPTED_DATA_TYPES_STRING);
throw new IllegalArgumentException(msg);
}
public String name() {
return this.name;
}
public Class<T> dataType() {
return this.dataType;
}
public String desc() {
return this.desc;
}
public boolean required() {
return this.required;
}
public R defaultValue() {
return this.convert(this.defaultValue);
}
@SuppressWarnings("unchecked")
public R convert(Object value) {
return (R) this.convert(value, this.dataType);
}
public Object convert(Object value, Class<?> dataType) {
if (dataType.equals(String.class)) {
return value;
}
// Use PropertyConverter method `toXXX` convert value
String methodTo = "to" + dataType.getSimpleName();
try {
Method method = PropertyConverter.class.getMethod(
methodTo, Object.class);
return method.invoke(null, value);
} catch (ReflectiveOperationException e) {
LOG.error("Invalid type of value '{}' for option '{}'",
value, this.name, e);
throw new ConfigException(
"Invalid type of value '%s' for option '%s', " +
"expect '%s' type",
value, this.name, dataType.getSimpleName());
}
}
public void check(Object value) {
E.checkNotNull(value, "value", this.name);
E.checkArgument(this.dataType.isInstance(value),
"Invalid type of value '%s' for option '%s'",
value, this.name);
if (this.checkFunc != null) {
@SuppressWarnings("unchecked")
T result = (T) value;
E.checkArgument(this.checkFunc.apply(result),
"Invalid option value for '%s': %s",
this.name, value);
}
}
@Override
public String toString() {
return String.format("[%s]%s=%s", this.dataType.getSimpleName(),
this.name, this.defaultValue);
}
}

View File

@ -27,5 +27,5 @@ public class CommonVersion {
// The second parameter of Version.of() is for all-in-one JAR
public static final Version VERSION = Version.of(CommonVersion.class,
"1.6.4");
"1.6.5");
}

View File

@ -21,6 +21,7 @@ package com.baidu.hugegraph.unit.config;
import static com.baidu.hugegraph.config.OptionChecker.allowValues;
import static com.baidu.hugegraph.config.OptionChecker.disallowEmpty;
import static com.baidu.hugegraph.config.OptionChecker.inValues;
import static com.baidu.hugegraph.config.OptionChecker.nonNegativeInt;
import static com.baidu.hugegraph.config.OptionChecker.positiveInt;
import static com.baidu.hugegraph.config.OptionChecker.rangeDouble;
@ -33,6 +34,8 @@ import org.apache.commons.configuration.PropertiesConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import com.baidu.hugegraph.config.ConfigConvOption;
import com.baidu.hugegraph.config.ConfigListConvOption;
import com.baidu.hugegraph.config.ConfigListOption;
import com.baidu.hugegraph.config.ConfigOption;
import com.baidu.hugegraph.config.HugeConfig;
@ -102,6 +105,10 @@ public class HugeConfigTest extends BaseUnitTest {
Assert.assertEquals(true, config.get(TestOptions.bool));
Assert.assertEquals(WeekDay.WEDNESDAY, config.get(TestOptions.weekday));
Assert.assertEquals(Arrays.asList(WeekDay.SATURDAY, WeekDay.SUNDAY),
config.get(TestOptions.weekdays));
Assert.assertEquals(Arrays.asList("list-value1", "list-value2"),
config.get(TestOptions.list));
@ -251,10 +258,30 @@ public class HugeConfigTest extends BaseUnitTest {
true
);
public static final ConfigConvOption<WeekDay> weekday =
new ConfigConvOption<>(
"group1.weekday",
"description of group1.weekday",
allowValues("SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
"THURSDAY", "FRIDAY", "SATURDAY"),
WeekDay::valueOf,
"WEDNESDAY"
);
public static final ConfigListConvOption<String, WeekDay> weekdays =
new ConfigListConvOption<>(
"group1.weekdays",
"description of group1.weekdays",
inValues("SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
"THURSDAY", "FRIDAY", "SATURDAY"),
WeekDay::valueOf,
String.class,
"SATURDAY", "SUNDAY"
);
public static final ConfigListOption<String> list =
new ConfigListOption<>(
"group1.list",
false,
"description of group1.list",
disallowEmpty(),
String.class,
@ -264,7 +291,6 @@ public class HugeConfigTest extends BaseUnitTest {
public static final ConfigListOption<String> map =
new ConfigListOption<>(
"group1.map",
false,
"description of group1.map",
disallowEmpty(),
String.class,
@ -290,4 +316,9 @@ public class HugeConfigTest extends BaseUnitTest {
"textsub-value"
);
}
public enum WeekDay {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
}