All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.lenni0451.commandlib.ArgumentChain Maven / Gradle / Ivy

The newest version!
package net.lenni0451.commandlib;

import net.lenni0451.commandlib.contexts.ExecutionContext;
import net.lenni0451.commandlib.exceptions.ArgumentParseException;
import net.lenni0451.commandlib.exceptions.ChainExecutionException;
import net.lenni0451.commandlib.exceptions.HandledException;
import net.lenni0451.commandlib.nodes.ArgumentNode;
import net.lenni0451.commandlib.nodes.RedirectNode;
import net.lenni0451.commandlib.utils.StringReader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * A chain of arguments required for correct execution.
 *
 * @param  The type of the executor
 */
public class ArgumentChain {

    /**
     * Build a list of all chains for the given argument node.
     *
     * @param argument The argument node
     * @param       The type of the executor
     * @return The list of chains
     * @throws IllegalArgumentException If a duplicate argument name is found or the chain end has no executor
     */
    public static  List> buildChains(final ArgumentNode argument) {
        List> chains = new ArrayList<>();

        Map, ArgumentChain> branches = new HashMap<>();
        branches.put(argument, new ArgumentChain<>(argument));
        while (!branches.isEmpty()) {
            Map, ArgumentChain> newBranches = new HashMap<>();
            for (Map.Entry, ArgumentChain> entry : branches.entrySet()) {
                ArgumentNode node = entry.getKey();
                ArgumentChain chain = entry.getValue();

                if (node.executor() != null || node instanceof RedirectNode) {
                    List names = new ArrayList<>();
                    for (ArgumentNode argumentNode : chain.arguments) {
                        if (!argumentNode.providesArgument()) continue;
                        if (names.contains(argumentNode.name())) throw new IllegalArgumentException("Duplicate argument name '" + argumentNode.name() + "' in chain: " + chain);
                        names.add(argumentNode.name());
                    }
                    chains.add(chain);
                } else if (node.children().isEmpty()) {
                    throw new IllegalStateException("Chain ended but has no executor: " + chain);
                }
                for (ArgumentNode child : node.children()) {
                    ArgumentChain newChain = new ArgumentChain<>(chain);
                    newChain.addArgument(child);
                    newBranches.put(child, newChain);
                }
            }
            branches = newBranches;
        }

        return chains;
    }

    public static  ArgumentChain merge(final ArgumentChain chain1, final ArgumentChain chain2) {
        if (chain1.getExecutor() != null) throw new IllegalArgumentException("Can not merge chains if the first chain does not end with a redirect node");
        if (chain2.getExecutor() == null) throw new IllegalArgumentException("Can not merge chains if the second chain does not have an executor");
        return new ArgumentChain() {
            @Override
            public int getLength() {
                return chain1.getLength() + chain2.getLength();
            }

            @Override
            public int[] getWeights() {
                int[] weights = new int[getLength()];
                System.arraycopy(chain1.getWeights(), 0, weights, 0, chain1.getLength());
                System.arraycopy(chain2.getWeights(), 0, weights, chain1.getLength(), chain2.getLength());
                return weights;
            }

            @Override
            public ArgumentNode getArgument(int index) {
                if (index < chain1.getLength()) return chain1.getArgument(index);
                return chain2.getArgument(index - chain1.getLength());
            }

            @Override
            public List parse(ExecutionContext executionContext, StringReader reader) {
                throw new UnsupportedOperationException("Can not parse a merged chain");
            }

            @Override
            public void populateArguments(ExecutionContext executionContext, List arguments) {
                chain1.populateArguments(executionContext, arguments.subList(0, chain1.getLength()));
                chain2.populateArguments(executionContext, arguments.subList(chain1.getLength(), arguments.size()));
            }

            @Override
            public Function, ?> getExecutor() {
                return chain2.getExecutor();
            }
        };
    }


    private final List> arguments;

    private ArgumentChain() {
        this.arguments = new ArrayList<>();
    }

    private ArgumentChain(final List> arguments) {
        this.arguments = arguments;
    }

    private ArgumentChain(final ArgumentNode argument) {
        this.arguments = new ArrayList<>();
        this.arguments.add(argument);
    }

    private ArgumentChain(final ArgumentChain chain) {
        this.arguments = new ArrayList<>(chain.arguments);
    }

    private void addArgument(final ArgumentNode argument) {
        this.arguments.add(argument);
    }

    public int getLength() {
        return this.arguments.size();
    }

    /**
     * @return The weights of all arguments in this chain
     */
    public int[] getWeights() {
        int[] weights = new int[this.arguments.size()];
        for (int i = 0; i < this.arguments.size(); i++) weights[i] = this.arguments.get(i).weight();
        return weights;
    }

    /**
     * Get an argument node by its index.
     *
     * @param index The index
     * @return The argument node
     * @throws IndexOutOfBoundsException If the index is out of bounds
     */
    public ArgumentNode getArgument(final int index) {
        return this.arguments.get(index);
    }

    /**
     * Parse the given input and return the parsed arguments.
     *
     * @param executionContext The execution context
     * @param reader           The input reader
     * @return The parsed arguments
     * @throws ChainExecutionException If the input can not be parsed
     */
    public List parse(final ExecutionContext executionContext, final StringReader reader) throws ChainExecutionException {
        List out = new ArrayList<>();
        for (int i = 0; i < this.arguments.size(); i++) {
            int cursor = reader.getCursor();
            ArgumentNode argument = this.arguments.get(i);
            boolean isLast = i == this.arguments.size() - 1;
            try {
                if (!argument.requirement().test(executionContext)) {
                    throw new ChainExecutionException(ChainExecutionException.Reason.REQUIREMENT_FAILED, i, cursor, argument.name(), reader.readRemaining());
                }
                if (argument instanceof RedirectNode) {
                    out.add(new MatchedArgument(cursor, "", argument.name()));
                    return out;
                }
                Object parsedArgument = argument.value(executionContext, reader);
                out.add(new MatchedArgument(cursor, reader.getString().substring(cursor, reader.getCursor()), parsedArgument));
                if (!isLast && (!reader.canRead() || reader.read() != ' ')) {
                    throw new ChainExecutionException(ChainExecutionException.Reason.MISSING_SPACE, i, cursor, null, reader.readRemaining());
                }
                if (!isLast && !reader.canRead()) {
                    ArgumentNode nextArgument = this.arguments.get(i + 1);
                    if (!nextArgument.requirement().test(executionContext)) {
                        throw new ChainExecutionException(ChainExecutionException.Reason.REQUIREMENT_FAILED, i + 1, cursor, nextArgument.name(), reader.readRemaining());
                    }
                    String missingArguments = new ArgumentChain<>(this.arguments.subList(i + 1, this.arguments.size())).toString();
                    throw new ChainExecutionException(ChainExecutionException.Reason.NO_ARGUMENTS_LEFT, i + 1, reader.getCursor(), null, missingArguments);
                } else if (isLast && reader.canRead()) {
                    throw new ChainExecutionException(ChainExecutionException.Reason.TOO_MANY_ARGUMENTS, i, reader.getCursor(), null, reader.readRemaining());
                }
            } catch (HandledException e) {
                throw new ChainExecutionException(e, i, cursor, argument.name(), reader.getString().substring(cursor, reader.getCursor()));
            } catch (ArgumentParseException e) {
                throw new ChainExecutionException(e, i, cursor, argument.name(), reader.getString().substring(cursor, reader.getCursor()));
            } catch (RuntimeException e) {
                throw new ChainExecutionException(e, i, cursor, argument.name(), reader.getString().substring(cursor, reader.getCursor()));
            }
        }
        return out;
    }

    /**
     * Populate the given execution context with the given arguments.
     *
     * @param executionContext The execution context
     * @param arguments        The arguments
     */
    public void populateArguments(final ExecutionContext executionContext, final List arguments) {
        for (int i = 0; i < this.arguments.size(); i++) {
            ArgumentNode argumentNode = this.arguments.get(i);
            if (!argumentNode.providesArgument()) continue;
            executionContext.addArgument(argumentNode.name(), arguments.get(i).getValue());
        }
    }

    /**
     * Get the executor of this chain.
     *
     * @return The executor
     */
    public Function, ?> getExecutor() {
        return this.arguments.get(this.arguments.size() - 1).executor();
    }

    @Override
    public String toString() {
        return this.toString(this.getExecutor() != null);
    }

    public String toString(final boolean skipRedirects) {
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < this.getLength(); i++) {
            ArgumentNode argument = this.getArgument(i);
            if (argument instanceof RedirectNode) {
                if (skipRedirects) continue;
                else out.append("(").append(argument.name()).append(")").append("->");
            } else if (argument.providesArgument()) {
                out.append('<').append(argument.name()).append('>');
            } else {
                out.append(argument.name());
            }
            out.append(' ');
        }
        return out.toString().trim();
    }


    /**
     * A wrapper for an argument that has been matched.
     */
    public static class MatchedArgument {
        private final int cursor;
        private final String match;
        private final Object value;

        private MatchedArgument(final int cursor, final String match, final Object value) {
            this.cursor = cursor;
            this.match = match;
            this.value = value;
        }

        /**
         * @return The cursor position of the start of the match
         */
        public int getCursor() {
            return this.cursor;
        }

        /**
         * @return The matched string
         */
        public String getMatch() {
            return this.match;
        }

        /**
         * @return The parsed value
         */
        public Object getValue() {
            return this.value;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy