/* * 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); } }