one.nio.config.ConfigParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of one-nio Show documentation
Show all versions of one-nio Show documentation
Unconventional Java I/O library
The newest version!
/*
* Copyright 2015-2016 Odnoklassniki Ltd, Mail.Ru Group
*
* 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 one.nio.config;
import one.nio.util.JavaInternals;
import java.lang.reflect.AnnotatedArrayType;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
// Parse YAML-like configuration files into Java objects.
// Sample configuration file:
//
// keepAlive: 120s
// maxWorkers: 1000
// queueTime: 50ms
//
// acceptors:
// - port: 443
// backlog: 10000
// deferAccept: true
// ssl:
// protocols: TLSv1+TLSv1.1+TLSv1.2
// certFile: /etc/ssl/my.crt
// privateKeyFile: /etc/ssl/my.key
//
// - port: 80
// backlog: 10000
// deferAccept: true
//
public class ConfigParser {
private final StringTokenizer st;
private final Map references;
private String line;
private int indent;
private ConfigParser(String config) {
this.st = new StringTokenizer(config, "\r\n");
this.references = new HashMap<>();
this.indent = -1;
}
public static T parse(String config, Class type) {
return parse(config, (Type) type);
}
@SuppressWarnings("unchecked")
public static T parse(String config, Type type) {
ConfigParser parser = new ConfigParser(config);
if (parser.nextLine() < 0) {
throw new IllegalArgumentException("Unexpected end of input");
}
try {
return (T) parser.parseValue(type, null, 0);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException(e);
}
}
private Object parseValue(Type type, AnnotatedType aType, int level) throws ReflectiveOperationException {
if (type instanceof Class) {
Class> cls = (Class) type;
if (cls.isArray()) {
Object ref = parseReference();
return ref != null ? ref : parseArray(cls.getComponentType(), aType, level);
} else if (hasScalarConverter(cls, aType)) {
return parseScalar(cls, aType, tail());
} else if (cls.isAnnotationPresent(Config.class)) {
Object ref = parseReference();
return ref != null ? ref : parseBean(cls, level);
}
Method method = JavaInternals.findMethod(cls, "valueOf", String.class);
if (method != null && (method.getModifiers() & Modifier.STATIC) != 0 && cls.isAssignableFrom(method.getReturnType())) {
return method.invoke(null, tail());
}
} else if (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) type;
Class> rawType = (Class>) ptype.getRawType();
if (Collection.class.isAssignableFrom(rawType) && ptype.getActualTypeArguments().length >= 1) {
Object ref = parseReference();
return ref != null ? ref : parseCollection(rawType, ptype.getActualTypeArguments()[0], aType, level);
} else if (Map.class.isAssignableFrom(rawType) && ptype.getActualTypeArguments().length >= 2) {
Object ref = parseReference();
Type[] mapArgs = ptype.getActualTypeArguments();
return ref != null ? ref : parseMap(rawType, aType, mapArgs[0], mapArgs[1], level);
}
} else if (type instanceof GenericArrayType) {
Object ref = parseReference();
return ref != null ? ref : parseArray(((GenericArrayType) type).getGenericComponentType(), aType, level);
}
throw new IllegalArgumentException("Invalid type: " + type);
}
private Object parseBean(Class> type, int minLevel) throws ReflectiveOperationException {
Object obj = type.getDeclaredConstructor().newInstance();
registerReference(obj);
fillBean(obj, minLevel);
return obj;
}
private void fillBean(Object obj, int minLevel) throws ReflectiveOperationException {
Map fields = collectFields(obj.getClass());
int level = nextLine();
if (level >= minLevel) {
do {
int colon = line.indexOf(':', level);
if (colon < 0) throw new IllegalArgumentException("Field expected: " + line);
String key = line.substring(level, colon).trim();
Field field = fields.get(key);
if (field == null) {
throw new IllegalArgumentException("Unknown field: " + line);
}
skipSpaces(colon + 1);
Object value;
Converter converter = field.getAnnotation(Converter.class);
// Declaration annotation and array type annotation are at the same location
// https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.4
if (!field.getType().isArray() && converter != null) {
value = convert(tail(), converter);
} else {
value = parseValue(field.getGenericType(), field.getAnnotatedType(), level + 1);
}
field.set(obj, value);
} while (isSameLevel(nextLine(), level, minLevel));
}
}
private Object convert(String value, Converter converter) throws ReflectiveOperationException {
Class> cls = converter.value();
Method method = JavaInternals.findMethodRecursively(cls, converter.method(), String.class);
if (method == null) {
throw new IllegalArgumentException("Invalid converter class: " + cls.getName());
}
Object sender = (method.getModifiers() & Modifier.STATIC) != 0 ? null : cls.getDeclaredConstructor().newInstance();
return method.invoke(sender, value);
}
private Object parseArray(Type elementType, AnnotatedType aType, int minLevel) throws ReflectiveOperationException {
if (aType instanceof AnnotatedArrayType) {
aType = ((AnnotatedArrayType) aType).getAnnotatedGenericComponentType();
}
List