    001/*
    002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License
    003 *
    004 *  Permission is hereby granted, free of charge, to any person obtaining
    005 *  a copy of this software and associated documentation files (the
    006 *  "Software"), to deal in the Software without restriction, including
    007 *  without limitation the rights to use, copy, modify, merge, publish,
    008 *  distribute, sublicense, and/or sell copies of the Software, and to
    009 *  permit persons to whom the Software is furnished to do so, subject to
    010 *  the following conditions:
    011 *
    012 *  The above copyright notice and this permission notice shall be
    013 *  included in all copies or substantial portions of the Software.
    014 *
    015 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    016 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    017 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    018 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    019 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    020 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    021 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    022 */
    023
    024package co.aikar.commands;
    025
    026import org.jetbrains.annotations.NotNull;
    027
    028import java.util.ArrayList;
    029import java.util.Arrays;
    030import java.util.Collection;
    031import java.util.Collections;
    032import java.util.HashMap;
    033import java.util.List;
    034import java.util.Map;
    035import java.util.function.Supplier;
    036import java.util.stream.Collectors;
    037import java.util.stream.IntStream;
    038
    039
    040@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
    041public class CommandCompletions<C extends CommandCompletionContext> {
    042    private final CommandManager manager;
    043    private Map<String, CommandCompletionHandler> completionMap = new HashMap<>();
    044    private Map<Class, String> defaultCompletions = new HashMap<>();
    045
    046    public CommandCompletions(CommandManager manager) {
    047        this.manager = manager;
    048        registerAsyncCompletion("nothing", c -> Collections.emptyList());
    049        registerAsyncCompletion("range", (c) -> {
    050            String config = c.getConfig();
    051            if (config == null) {
    052                return Collections.emptyList();
    053            }
    054            final String[] ranges = ACFPatterns.DASH.split(config);
    055            int start;
    056            int end;
    057            if (ranges.length != 2) {
    058                start = 0;
    059                end = ACFUtil.parseInt(ranges[0], 0);
    060            } else {
    061                start = ACFUtil.parseInt(ranges[0], 0);
    062                end = ACFUtil.parseInt(ranges[1], 0);
    063            }
    064            return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
    065        });
    066        List<String> timeunits = Arrays.asList("minutes", "hours", "days", "weeks", "months", "years");
    067        registerAsyncCompletion("timeunits", (c) -> timeunits);
    068    }
    069
    070    /**
    071     * Registr a completion handler to provide command completions based on the user input.
    072     *
    073     * @param id
    074     * @param handler
    075     * @return
    076     */
    077    public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) {
    078        return this.completionMap.put("@" + id.toLowerCase(), handler);
    079    }
    080
    081    /**
    082     * Registr a completion handler to provide command completions based on the user input.
    083     * This handler is declared to be safe to be executed asynchronously.
    084     * <p>
    085     * Not all platforms support this, so if the platform does not support asynchronous execution,
    086     * your handler will be executed on the main thread.
    087     * <p>
    088     * Use this anytime your handler does not need to access state that is not considered thread safe.
    089     * <p>
    090     * Use context.isAsync() to determine if you are async or not.
    091     *
    092     * @param id
    093     * @param handler
    094     * @return
    095     */
    096    public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) {
    097        return this.completionMap.put("@" + id.toLowerCase(), handler);
    098    }
    099
    100    /**
    101     * Register a static list of command completions that will never change.
    102     * Like @CommandCompletion, values are | (PIPE) separated.
    103     * <p>
    104     * Example: foo|bar|baz
    105     *
    106     * @param id
    107     * @param list
    108     * @return
    109     */
    110    public CommandCompletionHandler registerStaticCompletion(String id, String list) {
    111        return registerStaticCompletion(id, ACFPatterns.PIPE.split(list));
    112    }
    113
    114    /**
    115     * Register a static list of command completions that will never change
    116     *
    117     * @param id
    118     * @param completions
    119     * @return
    120     */
    121    public CommandCompletionHandler registerStaticCompletion(String id, String[] completions) {
    122        return registerStaticCompletion(id, Arrays.asList(completions));
    123    }
    124
    125    /**
    126     * Register a static list of command completions that will never change. The list is obtained from the supplier
    127     * immediately as part of this method call.
    128     *
    129     * @param id
    130     * @param supplier
    131     * @return
    132     */
    133    public CommandCompletionHandler registerStaticCompletion(String id, Supplier<Collection<String>> supplier) {
    134        return registerStaticCompletion(id, supplier.get());
    135    }
    136
    137    /**
    138     * Register a static list of command completions that will never change
    139     *
    140     * @param id
    141     * @param completions
    142     * @return
    143     */
    144    public CommandCompletionHandler registerStaticCompletion(String id, Collection<String> completions) {
    145        return registerAsyncCompletion(id, x -> completions);
    146    }
    147
    148    /**
    149     * @param id
    150     * @param classes
    151     * @return
    152     * @deprecated Feature Not done yet
    153     */
    154    CommandCompletionHandler setDefaultCompletion(String id, Class... classes) {
    155        // get completion with specified id
    156        id = id.toLowerCase();
    157        CommandCompletionHandler completion = completionMap.get(id);
    158
    159        if (completion == null) {
    160            // Throw something because no completion with specified id
    161            throw new IllegalStateException("Completion not registered for " + id);
    162        }
    163
    164        for (Class clazz : classes) {
    165            defaultCompletions.put(clazz, id);
    166        }
    167
    168        return completion;
    169    }
    170
    171    @NotNull
    172    List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) {
    173        String[] completions = ACFPatterns.SPACE.split(cmd.complete);
    174        final int argIndex = args.length - 1;
    175
    176        String input = args[argIndex];
    177
    178        String completion = argIndex < completions.length ? completions[argIndex] : null;
    179        if (completion == null && completions.length > 0) {
    180            completion = completions[completions.length - 1];
    181        }
    182        if (completion == null) {
    183            return Collections.singletonList(input);
    184        }
    185
    186        return getCompletionValues(cmd, sender, completion, args, isAsync);
    187    }
    188
    189    List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) {
    190        completion = manager.getCommandReplacements().replace(completion);
    191
    192        List<String> allCompletions = new ArrayList<>();
    193        String input = args.length > 0 ? args[args.length - 1] : "";
    194
    195        for (String value : ACFPatterns.PIPE.split(completion)) {
    196            String[] complete = ACFPatterns.COLONEQUALS.split(value, 2);
    197            CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
    198            if (handler != null) {
    199                if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) {
    200                    ACFUtil.sneaky(new SyncCompletionRequired());
    201                    return null;
    202                }
    203                String config = complete.length == 1 ? null : complete[1];
    204                CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args);
    205
    206                try {
    207                    //noinspection unchecked
    208                    Collection<String> completions = handler.getCompletions(context);
    209                    if (completions != null) {
    210                        allCompletions.addAll(completions);
    211                        continue;
    212                    }
    213                    //noinspection ConstantIfStatement,ConstantConditions
    214                    if (false) { // Hack to fool compiler. since its sneakily thrown.
    215                        throw new CommandCompletionTextLookupException();
    216                    }
    217                } catch (CommandCompletionTextLookupException ignored) {
    218                    // This should only happen if some other feedback error occured.
    219                } catch (Exception e) {
    220                    command.handleException(sender, Arrays.asList(args), e);
    221                }
    222                // Something went wrong in lookup, fall back to input
    223                return Collections.singletonList(input);
    224            } else {
    225                // Plaintext value
    226                allCompletions.add(value);
    227            }
    228        }
    229        return allCompletions;
    230    }
    231
    232    public interface CommandCompletionHandler<C extends CommandCompletionContext> {
    233        Collection<String> getCompletions(C context) throws InvalidCommandArgument;
    234    }
    235
    236    public interface AsyncCommandCompletionHandler<C extends CommandCompletionContext> extends CommandCompletionHandler<C> {
    237    }
    238
    239    public static class SyncCompletionRequired extends RuntimeException {
    240    }
    241
    242}