com.flowpowered.commands.Command.java Source code

Java tutorial

Introduction

Here is the source code for com.flowpowered.commands.Command.java

Source

/*
 * This file is part of Flow Commands, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2013 Spout LLC <https://spout.org/>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.flowpowered.commands;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import com.flowpowered.commands.exception.InsufficientPermissionsException;
import com.flowpowered.commands.exception.UnknownSubcommandException;
import com.flowpowered.commands.filter.CommandFilter;
import com.flowpowered.commons.Named;

public class Command implements Named {
    private final String name;
    private final String simpleName;
    protected final ReadWriteLock childLock = new ReentrantReadWriteLock();
    protected final ReadWriteLock aliasLock = new ReentrantReadWriteLock();
    private final CommandManager manager;
    private final Map<String, Command> children = new HashMap<>();
    private final Map<String, Alias> aliases = new HashMap<>();
    private final SortedSet<CommandFilter> filters = new ConcurrentSkipListSet<>();
    private CommandExecutor executor;
    private String permission;
    private String help, usage, desc;

    protected Command(String name, CommandManager manager) {
        this.simpleName = manager.normalizeChildName(getSimpleName(name));
        this.name = name.toLowerCase(Locale.ENGLISH);
        this.manager = manager;
    }

    /**
     * Processes this can for the specific {@link ProcessingMode}. This first applies filters, calls {@code ProcessingMode.step}, then processes children and aliases in the same way.
     *
     * @param sender the sender of the command
     * @param args the arguments passed
     * @param mode the mode in which to process
     * @throws CommandException
     */
    protected void process(CommandSender sender, CommandArguments args, ProcessingMode mode)
            throws CommandException {
        if (!hasPermission(sender)) {
            throw new InsufficientPermissionsException("Not enough permissions to execute this command.",
                    this.permission);
        }

        for (CommandFilter filter : this.filters) {
            filter.validate(this, sender, args);
        }

        if (mode.step(this, sender, args)) {
            return;
        }
        processChild(sender, args, mode);
    }

    protected void processChild(CommandSender sender, CommandArguments args, ProcessingMode mode)
            throws CommandException {
        String childName;
        childName = args.popSubCommand(); // No need to normalize here, as we use getChild(), which normalizes the name itself.
        this.childLock.readLock().lock();
        try {
            Command child = getChild(childName);
            if (child != null) {
                child.process(sender, args, mode);
                return;
            }
        } finally {
            this.childLock.readLock().unlock();
        }
        this.aliasLock.readLock().lock();
        try {
            Alias alias = this.aliases.get(manager.normalizeChildName(childName)); // Just need to normalize here, cause we directly access the map.
            if (alias != null) {
                alias.process(sender, args, mode);
                return;
            }
        } finally {
            this.aliasLock.readLock().unlock();
        }
        throw new UnknownSubcommandException(this, args.getPastCommandString(), childName);
    }

    /**
     * Gets the command that is the descendant of this command for a certain path.
     *
     * @param path
     * @return
     * @throws CommandException
     */
    public Command getDescendant(List<String> path) throws CommandException {
        Get getter = new Command.Get();
        process(null, new CommandArguments(path), getter);
        return getter.getCommand();
    }

    /**
     * Executes this command with the specified args, using the supplied {@link CommandExecutor}.
     *
     * @param sender
     * @param args
     * @throws CommandException
     */
    public void execute(CommandSender sender, CommandArguments args) throws CommandException {
        process(sender, args, EXECUTE);
    }

    /**
     * {@code ProcessingMode} allows different types of processing in {@code process}.
     */
    protected static interface ProcessingMode {
        /**
         * @return {@code true} if the command processing is done, {@code false} if child commands can be called
         */
        boolean step(Command command, CommandSender sender, CommandArguments args) throws CommandException;
    }

    protected static final Execute EXECUTE = new Execute();

    protected static class Execute implements ProcessingMode {
        @Override
        public boolean step(Command command, CommandSender sender, CommandArguments args) throws CommandException {
            CommandExecutor executor = command.getExecutor();
            return executor != null && executor.execute(command, sender, args);
        }
    }

    protected static class Get implements ProcessingMode {
        private Command command = null;

        @Override
        public boolean step(Command command, CommandSender sender, CommandArguments args) throws CommandException {
            if (args.hasMore()) {
                return false;
            }
            this.command = command;
            return true;
        }

        public Command getCommand() {
            return this.command;
        }
    }

    // ---------- Basic properties

    @Override
    public String getName() {
        return this.name;
    }

    /**
     * 
     * @return the simple name of this command, void of any periods or colons.
     */
    public String getSimpleName() {
        return this.simpleName;
    }

    public CommandExecutor getExecutor() {
        return this.executor;
    }

    public void setExecutor(CommandExecutor executor) {
        this.executor = executor;
    }

    public String getPermission() {
        return this.permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public boolean hasPermission(CommandSender sender) {
        String permission = this.permission;
        if (permission == null || sender == null) {
            return true;
        }
        return sender.hasPermission(permission);
    }

    public String getHelp() {
        return this.help;
    }

    public void setHelp(String help) {
        this.help = help;
    }

    public String getDescription() {
        return this.desc;
    }

    public void setDescription(String description) {
        this.desc = description;
    }

    public String getUsage() {
        return this.usage;
    }

    public void setUsage(String usage) {
        this.usage = usage;
    }

    public Set<CommandFilter> getFilters() {
        return Collections.unmodifiableSortedSet(this.filters);
    }

    public boolean hasFilter(CommandFilter filter) {
        return this.filters.contains(filter);
    }

    public boolean addFilter(CommandFilter filter) {
        return this.filters.add(filter);
    }

    public boolean addFilters(CommandFilter... filter) {
        return this.filters.addAll(Arrays.asList(filter));
    }

    public boolean removeFilter(CommandFilter filter) {
        return this.filters.remove(filter);
    }

    public CommandManager getManager() {
        return this.manager;
    }

    /**
     * Clears the executor and filters of this command.
     */
    public void clear() {
        setExecutor(null);
        this.filters.clear();
    }

    // ---------- Children

    public Map<String, Command> getChildren() {
        return Collections.unmodifiableMap(this.children);
    }

    /**
     * This tries to return the child with the given name as smartly as possible.
     * If the full name of the command is given, it will check first if that exists. If it doesn't, it checks if the child is mapped by simple name.
     * If the short name of the command is given, it will only check for that.
     *
     * @param name
     * @return
     */
    public Command getChild(String name) {
        name = manager.normalizeChildName(name);
        this.childLock.readLock().lock();
        try {
            Command get = this.children.get(name);
            try {
                return get == null ? this.children.get(getSimpleName(name)) : get;
            } catch (IllegalArgumentException e) {
            }
            return null;
        } finally {
            this.childLock.readLock().unlock();
        }
    }

    public boolean hasChild(String name) {
        name = manager.normalizeChildName(name);
        this.childLock.readLock().lock();
        try {
            return this.children.containsKey(name);
        } finally {
            this.childLock.readLock().unlock();
        }
    }

    /**
     * This maps {@code command} to {@code name} as a child, moving any existing command to use its full name instead.
     *
     * @param name the name to map {@code command} to
     * @param command the command to add as a child
     */
    public void insertChild(String name, Command command) {
        if (command == null) {
            throw new IllegalArgumentException("Invalid command! Must not be null!");
        }
        if (command.getManager() != this.manager) {
            throw new IllegalArgumentException("Tried to put command from different manager.");
        }
        name = manager.normalizeChildName(name);
        this.childLock.writeLock().lock();
        try {
            Command old = this.children.put(name, command);
            this.manager.onCommandChildChange(this, name, old, command);
            if (old != null && old != command) {
                this.manager.onCommandChildChange(this, old.getName(), this.children.put(old.getName(), old), old);
            }
        } finally {
            this.childLock.writeLock().unlock();
        }
    }

    /**
     * This maps {@code command} to {@code name} as a child. However, if an existing command is mapped to {@code name}, the {@code command} is not mapped.
     *
     * @param name the name to map {@code command} to
     * @param command the command to add as a child
     * @return the old command or {@code null} if none existed and {@code command} was successfully mapped
     */
    public Command addChildIfAbsent(String name, Command command) {
        if (name == null) {
            throw new IllegalArgumentException("Invalid name! Must not be null!");
        }
        if (command == null) {
            throw new IllegalArgumentException("Invalid command! Must not be null!");
        }
        if (command.getManager() != this.manager) {
            throw new IllegalArgumentException("Tried to put command from different manager.");
        }
        name = manager.normalizeChildName(name);
        this.childLock.writeLock().lock();
        try {
            Command previous = this.children.get(name);
            if (previous == null) {
                this.children.put(name, command);
                this.manager.onCommandChildChange(this, name, previous, command);
            }
            return previous;
        } finally {
            this.childLock.writeLock().unlock();
        }
    }

    /**
     * This maps {@code command} to {@code name} as a child. However, if the child already exists, an {@link ChildAlreadyExistException} is thrown.
     *
     * @param name the name to map {@code command} to
     * @param command the command to add as a child
     * @throws ChildAlreadyExistException
     */
    public void addChild(String name, Command command) throws ChildAlreadyExistException {
        if (name == null) {
            throw new IllegalArgumentException("Invalid name! Must not be null!");
        }
        if (command == null) {
            throw new IllegalArgumentException("Invalid command! Must not be null!");
        }
        if (command.getManager() != this.manager) {
            throw new IllegalArgumentException("Tried to put command from different manager.");
        }
        name = manager.normalizeChildName(name);
        this.childLock.writeLock().lock();
        try {
            if (this.children.get(name) != null) {
                throw new ChildAlreadyExistException(
                        "Child already exists for name: " + name + " for command: " + this.name);
            }
            this.children.put(name, command);
            this.manager.onCommandChildChange(this, name, null, command);
        } finally {
            this.childLock.writeLock().unlock();
        }
    }

    /**
     * This maps {@code command} as a child.
     * It first checks if there is a command mapped to {@code command.getSimpleName()}. If there is not, it maps {@code command} to {@code command.getSimpleName()}.
     * If there is, it maps {@code command} to {@code command.getName()}.
     *
     * @param command the command to add as a child
     */
    public void addChild(Command command) {
        if (command == null) {
            throw new IllegalArgumentException("Invalid command! Must not be null!");
        }
        if (command.getManager() != this.manager) {
            throw new IllegalArgumentException("Tried to put command from different manager.");
        }
        this.childLock.writeLock().lock();
        try {
            if (this.children.get(command.getSimpleName()) == null) {
                // TODO: logging?
                this.children.put(command.getSimpleName(), command);
                this.manager.onCommandChildChange(this, command.getSimpleName(), null, command);
            } else {
                Command old = this.children.put(command.getName(), command);
                this.manager.onCommandChildChange(this, command.getName(), old, command);
            }
        } finally {
            this.childLock.writeLock().unlock();
        }
    }

    /**
     * Removes the child with {@code name}.
     *
     * @param name
     * @return the removed child, or {@code null} if no command was mapped
     */
    public Command removeChild(String name) {
        name = manager.normalizeChildName(name);
        this.childLock.writeLock().lock();
        try {
            Command old = this.children.remove(name);
            this.manager.onCommandChildChange(this, name, old, null);
            return old;
        } finally {
            this.childLock.writeLock().unlock();
        }
    }

    /**
     * Gets the alias mapped to the specific name.
     *
     * @param name
     * @return
     */
    public Alias getAlias(String name) {
        name = manager.normalizeChildName(name);
        this.aliasLock.readLock().lock();
        try {
            return this.aliases.get(name);
        } finally {
            this.aliasLock.readLock().unlock();
        }
    }

    /**
     * Overwrites any existing Alias mapped to {@code name} with {@code alias}.
     *
     * @param name
     * @param alias
     */
    public void overwriteAlias(String name, Alias alias) {
        name = manager.normalizeChildName(name);
        this.aliasLock.writeLock().lock();
        try {
            Alias previous = this.aliases.put(name, alias);
            this.manager.onAliasChange(this, name, previous, alias);
        } finally {
            this.aliasLock.writeLock().unlock();
        }
    }

    /**
     * This maps {@code alias} to {@code name}. However, if the child already exists, an {@link AliasAlreadyCreatedException} is thrown.
     *
     * @param name the name to map {@code alias} to
     * @param alias the alias to add
     * @throws AliasAlreadyCreatedException
     */
    public void addAlias(String name, Alias alias) throws AliasAlreadyCreatedException {
        name = manager.normalizeChildName(name);
        this.aliasLock.writeLock().lock();
        try {
            Alias previous = this.aliases.get(name);
            if (previous != null && previous != alias) {
                throw new AliasAlreadyCreatedException(
                        "Alias already created for name: " + name + " for command: " + this.name);
            }
            this.aliases.put(name, alias);
            this.manager.onAliasChange(this, name, previous, alias);
        } finally {
            this.aliasLock.writeLock().unlock();
        }
    }

    /**
     * This maps {@code alias} to {@code name}. However, if an existing Alias is mapped to {@code name}, the {@code alias} is not mapped.
     *
     * @param name the name to map {@code alias} to
     * @param alias the alias to add
     * @return the old alias or {@code null} if none existed and {@code alias} was successfully mapped
     */
    public Alias addAliasIfAbsent(String name, Alias alias) {
        name = manager.normalizeChildName(name);
        this.aliasLock.writeLock().lock();
        try {
            Alias previous = this.aliases.get(name);
            if (previous == null) {
                this.aliases.put(name, alias);
                this.manager.onAliasChange(this, name, previous, alias);
            }
            return previous;
        } finally {
            this.aliasLock.writeLock().unlock();
        }
    }

    /**
     * Removes the alias with {@code name}.
     *
     * @param name
     * @return the alias that was removed or {@code null} if no alias was mapped
     */
    public Alias removeAlias(String name) {
        name = manager.normalizeChildName(name);
        this.aliasLock.writeLock().lock();
        try {
            Alias removed = this.aliases.remove(name);
            if (removed != null) {
                this.manager.onAliasChange(this, name, removed, null);
            }
            return removed;
        } finally {
            this.aliasLock.writeLock().unlock();
        }
    }

    public boolean hasAlias(String name) {
        name = manager.normalizeChildName(name);
        this.aliasLock.readLock().lock();
        try {
            return this.aliases.containsKey(name);
        } finally {
            this.aliasLock.readLock().unlock();
        }
    }

    public Map<String, Alias> getAliases() {
        return Collections.unmodifiableMap(this.aliases);
    }

    // ---------- Object overrides

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(this.name).toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Command)) {
            return false;
        }
        Command other = (Command) obj;
        return new EqualsBuilder().append(this.name, other.name).build();
    }

    public static String getSimpleName(String fullName) {
        String names[] = fullName.split(":");
        if (names.length != 2) {
            throw new IllegalArgumentException("Invalid command name! Must be full name!");
        }
        if (!names[1].contains(".")) {
            return names[1];
        }
        String[] path = names[1].split("\\.");
        return path[path.length - 1];
    }

    @Override
    public String toString() {
        return "Command{" + "name=" + this.name + '}';
    }

}