org.terasology.logic.console.commandSystem.AbstractCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.terasology.logic.console.commandSystem.AbstractCommand.java

Source

/*
 * Copyright 2014 MovingBlocks
 *
 * 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 org.terasology.logic.console.commandSystem;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.common.primitives.Primitives;
import org.terasology.context.Context;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.logic.console.commandSystem.exceptions.CommandExecutionException;
import org.terasology.logic.console.commandSystem.exceptions.CommandInitializationException;
import org.terasology.logic.console.commandSystem.exceptions.CommandParameterParseException;
import org.terasology.logic.console.commandSystem.exceptions.CommandSuggestionException;
import org.terasology.logic.permission.PermissionManager;
import org.terasology.naming.Name;
import org.terasology.utilities.reflection.SpecificAccessibleObject;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * The core ICommand implementation and command information
 *
 */
public abstract class AbstractCommand implements ConsoleCommand {

    private final Name name;
    private final String requiredPermission;
    private final boolean runOnServer;
    private final String description;
    private final String helpText;
    private final SpecificAccessibleObject<Method> executionMethod;
    private ImmutableList<CommandParameter> commandParameters;
    private ImmutableList<Parameter> executionMethodParameters;
    private int requiredParameterCount;
    private String usage;

    public AbstractCommand(Name name, String requiredPermission, boolean runOnServer, String description,
            String helpText, SpecificAccessibleObject<Method> executionMethod, Context context) {
        Preconditions.checkNotNull(executionMethod);
        Preconditions.checkNotNull(description);
        Preconditions.checkNotNull(helpText);

        this.name = name;
        this.requiredPermission = requiredPermission != null ? requiredPermission
                : PermissionManager.DEBUG_PERMISSION;
        this.runOnServer = runOnServer;
        this.description = description;
        this.helpText = helpText;
        this.executionMethod = executionMethod;

        constructParametersNotNull(context);
        registerParameters();
        validateExecutionMethod();
        initUsage();
    }

    /**
     * @return A list of parameter types provided to the execution method.
     */
    protected abstract List<Parameter> constructParameters(Context context);

    private void constructParametersNotNull(Context context) {
        List<Parameter> constructedParameters = constructParameters(context);

        if (constructedParameters == null || constructedParameters.size() <= 0) {
            commandParameters = ImmutableList.of();
            executionMethodParameters = ImmutableList.of();
            return;
        }

        ImmutableList.Builder<CommandParameter> commandParameterBuilder = ImmutableList.builder();

        for (int i = 0; i < constructedParameters.size(); i++) {
            Parameter type = constructedParameters.get(i);

            if (type == null) {
                throw new CommandInitializationException(
                        "Invalid parameter definition #" + i + "; must not be null");
            } else if (type instanceof CommandParameter) {
                commandParameterBuilder.add((CommandParameter) type);
            }
        }

        commandParameters = commandParameterBuilder.build();
        executionMethodParameters = ImmutableList.copyOf(constructedParameters);
    }

    private void registerParameters() throws CommandInitializationException {
        requiredParameterCount = 0;
        boolean optionalFound = false;

        for (int i = 0; i < commandParameters.size(); i++) {
            CommandParameter parameter = commandParameters.get(i);

            if (parameter == null) {
                throw new CommandInitializationException("A command parameter must not be null! Index: " + i);
            }

            if (parameter.isArray() && i < commandParameters.size() - 1) {
                throw new CommandInitializationException(
                        "A varargs parameter must be at the end. Invalid: " + i + "; " + parameter.getName());
            }

            if (parameter.isRequired()) {
                if (!optionalFound) {
                    requiredParameterCount++;
                } else {
                    throw new CommandInitializationException("A command definition must not contain a required"
                            + " parameter (" + i + "; " + parameter.getName() + ") after an optional parameter.");
                }
            } else if (!optionalFound) {
                optionalFound = true;
            }
        }
    }

    private void checkArgumentCompatibility(Method method) throws CommandInitializationException {
        Class<?>[] methodParameters = method.getParameterTypes();

        int executionMethodParametersSize = executionMethodParameters.size();
        int methodParameterCount = methodParameters.length;

        for (int i = 0; i < methodParameterCount || i < executionMethodParametersSize; i++) {
            if (i >= methodParameterCount) {
                throw new CommandInitializationException(
                        "Missing " + (executionMethodParametersSize - methodParameterCount)
                                + " parameters in method " + method.getName()
                                + ", follow the parameter definitions from the" + " 'constructParameters' method.");
            } else if (i >= executionMethodParametersSize) {
                throw new CommandInitializationException(
                        "Too many (" + (methodParameterCount - executionMethodParametersSize)
                                + ") parameters in method " + method.getName()
                                + ", follow the parameter definitions from the" + " 'constructParameters' method.");
            }

            Parameter expectedParameterType = executionMethodParameters.get(i);
            Optional<? extends Class<?>> expectedType = expectedParameterType.getProvidedType();
            Class<?> providedType = methodParameters[i];

            if (providedType.isPrimitive()) {
                providedType = Primitives.wrap(providedType);
            }

            if (expectedType.isPresent() && !expectedType.get().isAssignableFrom(providedType)) {
                throw new CommandInitializationException("Cannot assign command argument from "
                        + providedType.getSimpleName() + " to " + expectedType.get().getSimpleName() + "; "
                        + "command method parameter index: " + i);
            }
        }
    }

    private void validateExecutionMethod() {
        checkArgumentCompatibility(executionMethod.getAccessibleObject());
    }

    private void initUsage() {
        StringBuilder builder = new StringBuilder(name.toString());

        for (CommandParameter param : commandParameters) {
            builder.append(' ').append(param.getUsage());
        }

        usage = builder.toString();
    }

    private Object[] processParametersMethod(List<String> rawParameters, EntityRef sender)
            throws CommandParameterParseException {
        Object[] processedParameters = new Object[executionMethodParameters.size()];
        Queue<String> parameterStrings = Queues.newArrayDeque(rawParameters);

        for (int i = 0; i < executionMethodParameters.size(); i++) {
            Parameter parameterType = executionMethodParameters.get(i);

            if (parameterType instanceof CommandParameter) {
                CommandParameter parameter = (CommandParameter) parameterType;
                if (parameterStrings.isEmpty()) {
                    if (parameter.isArray()) {
                        processedParameters[i] = parameter.getArrayValue(Collections.<String>emptyList());
                    } else {
                        processedParameters[i] = null;
                    }
                } else if (parameter.isArray()) {
                    processedParameters[i] = parameter.getArrayValue(Lists.newArrayList(parameterStrings));
                    parameterStrings.clear();
                } else {
                    processedParameters[i] = parameter.getValue(parameterStrings.poll());
                }
            } else if (parameterType == MarkerParameters.SENDER) {
                processedParameters[i] = sender;
            }
        }

        return processedParameters;
    }

    @Override
    public final String execute(List<String> rawParameters, EntityRef sender) throws CommandExecutionException {
        Object[] processedParameters;

        try {
            processedParameters = processParametersMethod(rawParameters, sender);
        } catch (CommandParameterParseException e) {
            String warning = "Invalid parameter '" + e.getParameter() + "'";
            String message = e.getMessage();

            if (message != null) {
                warning += ": " + message;
            }

            return warning;
        }

        try {
            Object result = executionMethod.getAccessibleObject().invoke(executionMethod.getTarget(),
                    processedParameters);

            return result != null ? String.valueOf(result) : null;
        } catch (InvocationTargetException t) {
            if (t.getCause() != null) {
                throw new CommandExecutionException(t.getCause()); //Skip InvocationTargetException
            } else {
                throw new CommandExecutionException(t);
            }
        } catch (IllegalAccessException | RuntimeException t) {
            throw new CommandExecutionException(t);
        }
    }

    @Override
    public final Set<String> suggest(final String currentValue, List<String> rawParameters, EntityRef sender)
            throws CommandSuggestionException {
        //Generate an array to be used as a parameter in the 'suggest' method
        Object[] processedParameters;

        try {
            processedParameters = processParametersMethod(rawParameters, sender);
        } catch (CommandParameterParseException e) {
            String warning = "Invalid parameter '" + e.getParameter() + "'";
            String message = e.getMessage();

            if (message != null) {
                warning += ": " + message;
            }

            throw new CommandSuggestionException(warning);
        }

        //Get the suggested parameter to compare the result with
        CommandParameter suggestedParameter = null;
        Iterator<CommandParameter> paramIter = commandParameters.iterator();

        for (Object processedParameter : processedParameters) {
            if (sender.equals(processedParameter)) {
                continue;
            }
            if (processedParameter == null) {
                suggestedParameter = paramIter.next();
                break;
            }
            paramIter.next();
        }

        if (suggestedParameter == null) {
            return Sets.newHashSet();
        }

        Set<Object> result = null;

        result = suggestedParameter.suggest(sender, processedParameters);

        if (result == null) {
            return Sets.newHashSet();
        }

        Class<?> requiredClass = suggestedParameter.getType();

        for (Object resultComponent : result) {
            if (resultComponent == null && requiredClass.isPrimitive()) {
                throw new CommandSuggestionException(
                        "The 'suggest' method of command class " + getClass().getCanonicalName()
                                + " returns a collection containing an invalid type. Required: "
                                + requiredClass.getCanonicalName() + "; provided: null");
            } else if (resultComponent != null && !requiredClass.isAssignableFrom(resultComponent.getClass())) {
                throw new CommandSuggestionException(
                        "The 'suggest' method of command class " + getClass().getCanonicalName()
                                + " returns a collection containing an invalid type. Required: "
                                + requiredClass.getCanonicalName() + "; provided: "
                                + resultComponent.getClass().getCanonicalName());
            }
        }

        Set<String> stringSuggestions = convertToString(result, suggestedParameter);

        //Only return results starting with currentValue
        return Sets.filter(stringSuggestions,
                input -> input != null && (currentValue == null || input.startsWith(currentValue)));
    }

    private static Set<String> convertToString(Set<Object> collection, CommandParameter parameter) {
        return collection.stream().map(parameter::convertToString).collect(Collectors.toCollection(HashSet::new));
    }

    @Override
    public ImmutableList<CommandParameter> getCommandParameters() {
        return commandParameters;
    }

    @Override
    public boolean isRunOnServer() {
        return runOnServer;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public String getHelpText() {
        return helpText;
    }

    @Override
    public String getUsage() {
        return usage;
    }

    @Override
    public Name getName() {
        return name;
    }

    @Override
    public int getRequiredParameterCount() {
        return requiredParameterCount;
    }

    @Override
    public boolean endsWithVarargs() {
        return commandParameters.size() > 0 && commandParameters.get(commandParameters.size() - 1).isArray();
    }

    @Override
    public Object getSource() {
        return executionMethod.getTarget();
    }

    @Override
    public int compareTo(ConsoleCommand o) {
        return ConsoleCommand.COMPARATOR.compare(this, o);
    }

    @Override
    public String getRequiredPermission() {
        return requiredPermission;
    }

    public SpecificAccessibleObject<Method> getExecutionMethod() {
        return executionMethod;
    }
}