/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package de.steamwar.command;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@UtilityClass
public class SWCommandUtils {
@Getter
private final Map> MAPPER_FUNCTIONS = new HashMap<>();
@Getter
private final Map> VALIDATOR_FUNCTIONS = new HashMap<>();
private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() {
@Override
public Object map(Object sender, PreviousArguments previousArguments, String s) {
return mapper.apply(s);
}
@Override
public Collection tabCompletes(Object sender, PreviousArguments previousArguments, String s) {
return ((BiFunction>) tabCompleter).apply(sender, s);
}
};
static final AbstractTypeMapper STRING_MAPPER = createMapper(s -> s, Collections::singletonList);
static {
addMapper(boolean.class, Boolean.class, createMapper(s -> {
if (s.equalsIgnoreCase("true")) return true;
if (s.equalsIgnoreCase("false")) return false;
return null;
}, s -> Arrays.asList("true", "false")));
addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat, true)));
addMapper(double.class, Double.class, createMapper(numberMapper(Double::parseDouble), numberCompleter(Double::parseDouble, true)));
addMapper(int.class, Integer.class, createMapper(numberMapper(Integer::parseInt), numberCompleter(Integer::parseInt, false)));
addMapper(long.class, Long.class, createMapper(numberMapper(Long::parseLong), numberCompleter(Long::parseLong, false)));
MAPPER_FUNCTIONS.put(String.class.getTypeName(), STRING_MAPPER);
}
public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) {
SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator;
}
private static void addMapper(Class> clazz, Class> alternativeClazz, AbstractTypeMapper, ?> mapper) {
MAPPER_FUNCTIONS.put(clazz.getTypeName(), mapper);
MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper);
}
public static AbstractTypeMapper getTypeMapper(String name, Map> localTypeMapper) {
AbstractTypeMapper typeMapper = localTypeMapper.getOrDefault(name, (AbstractTypeMapper) MAPPER_FUNCTIONS.getOrDefault(name, null));
if (typeMapper == null) {
throw new IllegalArgumentException("No mapper found for " + name);
}
return typeMapper;
}
public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) {
Class> clazz = parameter.getType();
if (parameter.isVarArgs()) {
clazz = clazz.getComponentType();
}
if (clazz.isEnum() && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) {
return createEnumMapper((Class>) clazz);
}
return getTypeMapper(clazz.getTypeName(), localTypeMapper);
}
public static AbstractValidator getValidator(AbstractSWCommand.Validator validator, Class> type, Map> localValidator) {
String s = validator.value() != null && !validator.value().isEmpty() ? validator.value() : type.getTypeName();
AbstractValidator concreteValidator = localValidator.getOrDefault(s, (AbstractValidator) VALIDATOR_FUNCTIONS.getOrDefault(s, null));
if (concreteValidator == null) {
throw new IllegalArgumentException("No validator found for " + s);
}
return concreteValidator;
}
public static void addMapper(Class clazz, AbstractTypeMapper mapper) {
addMapper(clazz.getTypeName(), mapper);
}
public static void addMapper(String name, AbstractTypeMapper mapper) {
MAPPER_FUNCTIONS.putIfAbsent(name, mapper);
}
public static void addValidator(Class clazz, AbstractValidator validator) {
addValidator(clazz.getTypeName(), validator);
}
public static void addValidator(String name, AbstractValidator validator) {
VALIDATOR_FUNCTIONS.putIfAbsent(name, validator);
}
public static , K> T createMapper(String... values) {
List strings = Arrays.stream(values).map(String::toLowerCase).collect(Collectors.toList());
List tabCompletes = Arrays.asList(values);
return createMapper(s -> strings.contains(s.toLowerCase()) ? s : null, s -> tabCompletes);
}
public static , K, V> T createMapper(Function mapper, Function> tabCompleter) {
return createMapper(mapper, (commandSender, s) -> tabCompleter.apply(s));
}
public static , K, V> T createMapper(Function mapper, BiFunction> tabCompleter) {
return (T) swTypeMapperCreator.createTypeMapper(mapper, tabCompleter);
}
public static >, K> T createEnumMapper(Class> enumClass) {
Map> enumMap = new HashMap<>();
for (Enum> e : enumClass.getEnumConstants()) {
enumMap.put(e.name().toLowerCase(), e);
}
return createMapper(s -> enumMap.get(s.toLowerCase()), (k, s) -> enumMap.keySet());
}
private static Function numberMapper(Function mapper) {
return s -> {
if (s.equalsIgnoreCase("nan")) return null;
try {
return mapper.apply(s);
} catch (NumberFormatException e) {
// Ignored
}
try {
return mapper.apply(s.replace(',', '.'));
} catch (NumberFormatException e) {
return null;
}
};
}
private static Function> numberCompleter(Function mapper, boolean comma) {
return s -> {
if (numberMapper(mapper).apply(s) == null) {
return Collections.emptyList();
}
List strings = new ArrayList<>();
if (s.length() == 0) {
strings.add("-");
} else {
strings.add(s);
}
for (int i = 0; i < 10; i++) {
strings.add(s + i);
}
if (comma && (!s.contains(".") || !s.contains(","))) {
strings.add(s + ".");
}
return strings;
};
}
static T[] getAnnotation(Method method, Class annotation) {
if (method.getAnnotations().length == 0) return null;
return method.getDeclaredAnnotationsByType(annotation);
}
}