blob: 2aa50e3f729a6bb76f4daa273e4bf8f0943e42ba [file] [log] [blame]
/*
* 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,
Class.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);
}
public R parseConvert(String value) {
T parsed = this.parse(value);
this.check(parsed);
return this.convert(parsed);
}
@SuppressWarnings("unchecked")
protected T parse(String value) {
return (T) this.parse(value, this.dataType);
}
protected Object parse(String value, Class<?> dataType) {
if (dataType.equals(String.class)) {
return value;
} else if (dataType.equals(Class.class)) {
try {
return Class.forName(value);
} catch (ClassNotFoundException e) {
throw new ConfigException(
"Failed to parse Class from String '%s'", e, value);
}
} else if (List.class.isAssignableFrom(dataType)) {
E.checkState(this.forList(),
"List option can't be registered with class %s",
this.getClass().getSimpleName());
}
// 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());
}
}
protected void check(Object value) {
E.checkNotNull(value, "value", this.name);
if (!this.dataType.isInstance(value)) {
throw new ConfigException(
"Invalid type of value '%s' for option '%s', " +
"expect type %s but got %s", value, this.name,
this.dataType.getSimpleName(),
value.getClass().getSimpleName());
}
if (this.checkFunc != null) {
@SuppressWarnings("unchecked")
T result = (T) value;
if (!this.checkFunc.apply(result)) {
throw new ConfigException("Invalid option value for '%s': %s",
this.name, value);
}
}
}
@SuppressWarnings("unchecked")
protected R convert(T value) {
return (R) value;
}
protected boolean forList() {
return false;
}
@Override
public String toString() {
return String.format("[%s]%s=%s", this.dataType.getSimpleName(),
this.name, this.defaultValue);
}
}