001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.server.plugin;
019
020import com.google.common.collect.Collections2;
021import com.google.common.collect.ForwardingCollection;
022import com.google.common.collect.ImmutableSet;
023import com.google.common.collect.Sets;
024import net.tridentsdk.Console;
025import net.tridentsdk.Trident;
026import net.tridentsdk.entity.living.Player;
027import net.tridentsdk.meta.MessageBuilder;
028import net.tridentsdk.plugin.Plugin;
029import net.tridentsdk.plugin.PluginLoadException;
030import net.tridentsdk.plugin.annotation.CommandDesc;
031import net.tridentsdk.plugin.cmd.Command;
032import net.tridentsdk.plugin.cmd.CommandIssuer;
033import net.tridentsdk.plugin.cmd.Commands;
034import net.tridentsdk.server.concurrent.TickSync;
035import net.tridentsdk.util.TridentLogger;
036
037import java.util.Collection;
038import java.util.Map;
039import java.util.Set;
040import java.util.concurrent.ConcurrentHashMap;
041import java.util.stream.Collectors;
042
043/**
044 * Handles commands passed from the server
045 *
046 * @author The TridentSDK Team
047 * @since 0.4-alpha
048 */
049public class CommandHandler extends ForwardingCollection<Command> implements Commands {
050    // TODO: Make this a dictionary tree for fast lookup
051    private final Map<String, CommandData> commands = new ConcurrentHashMap<>();
052
053    public CommandHandler() {
054        if (!Trident.isTrident())
055            throw new RuntimeException(new IllegalAccessException("Only TridentSDK is allowed to make a new command handler"));
056    }
057
058    @Override
059    protected Collection<Command> delegate() {
060        return ImmutableSet.copyOf(Collections2.transform(commands.values(), CommandData::command));
061    }
062
063    @Override
064    public void handle(final String message, final CommandIssuer issuer) {
065        if (message.isEmpty()) {
066            return;
067        }
068
069        final String[] contents = message.split(" ");
070        final String label = contents[0].toLowerCase();
071        final String args = message.substring(label.length() + (message.contains(" ") ? 1 : 0));
072        final Set<CommandData> cmdData = findCommand(label);
073
074        if (!cmdData.isEmpty()) {
075            TickSync.sync(() -> {
076                for (CommandData data : cmdData) {
077                    handle(data.command(), issuer, args, contents, data);
078                }
079            });
080        } else {
081            // Command not found
082            if(issuer instanceof Player){
083                issuer.sendRaw(new MessageBuilder("Command not found").build().asJson());
084            }else{
085                issuer.sendRaw("Command not found");
086            }
087        }
088    }
089
090    private Set<CommandData> findCommand(String label) {
091        Set<CommandData> dataSet = Sets.newHashSet();
092        CommandData data = commands.get(label);
093
094        if (data != null) {
095            dataSet.add(data);
096        }
097
098        dataSet.addAll(commands.values().stream().filter(d -> d.hasAlias(label)).collect(Collectors.toList()));
099
100        return dataSet;
101    }
102
103    private void handle(Command cmd, CommandIssuer issuer, String args, String[] contents, CommandData data) {
104        if (!issuer.ownsPermission(data.permission)) {
105            issuer.sendRaw(new MessageBuilder("You do not have permission").build().asJson());
106            return;
107        }
108
109        if (issuer instanceof Player)
110            cmd.handlePlayer((Player) issuer, args, contents[0]);
111        else if (issuer instanceof Console)
112            cmd.handleConsole((Console) issuer, args, contents[0]);
113
114        cmd.handle(issuer, args, contents[0]);
115    }
116
117    @Override
118    public int register(Plugin plugin, Command command) {
119        CommandDesc description = command.getClass().getAnnotation(CommandDesc.class);
120
121        if (description == null) {
122            TridentLogger.get().error(new PluginLoadException(
123                    "Error in registering commands: Class does not have annotation " + "\"CommandDesc\"!"));
124            return 0;
125        }
126
127        String name = description.name();
128        int priority = description.priority();
129        String[] aliases = description.aliases();
130        String permission = description.permission();
131
132        if (name == null || "".equals(name)) {
133            TridentLogger.get().error(new PluginLoadException("cmd does not declare a valid name!"));
134            return 0;
135        }
136
137        String lowerCase = name.toLowerCase();
138        CommandData data = commands.get(lowerCase);
139        CommandData newData = new CommandData(name, priority, aliases, permission, command, plugin);
140
141        if (data != null) {
142            if (commands.get(lowerCase).priority() > priority) {
143                // put the new, more important cmd in place and notify the old cmd that it has been overridden
144                commands.put(lowerCase, newData).command().notifyOverriden();
145            } else {
146                // don't register this cmd and notify it has been overridden
147                command.notifyOverriden();
148            }
149        } else {
150            commands.put(name, newData);
151        }
152
153        // TODO: return something meaningful
154        return 0;
155    }
156
157    @Override
158    public void unregister(Class<? extends Command> cls) {
159        commands.entrySet().stream().filter(e -> e.getValue().command().getClass().equals(cls)).forEach(e -> commands.remove(e.getKey()));
160    }
161
162    private static class CommandData {
163        private final String permission;
164        private final int priority;
165        private final String[] aliases;
166        private final String name;
167        private final Command encapsulated;
168        private final Plugin plugin;
169
170        public CommandData(String name, int priority, String[] aliases, String permission, Command command,
171                           Plugin plugin) {
172            this.priority = priority;
173            this.name = name;
174            this.aliases = aliases;
175            this.permission = permission;
176            this.encapsulated = command;
177            this.plugin = plugin;
178        }
179
180        public Command command() {
181            return this.encapsulated;
182        }
183
184        public boolean hasAlias(String alias) {
185            if (name.equals(alias)) return true;
186
187            for (String string : this.aliases) {
188                if (alias.equalsIgnoreCase(string)) {
189                    return true;
190                }
191            }
192            return false;
193        }
194
195        public int priority() {
196            return this.priority;
197        }
198
199        public Plugin plugin() {
200            return plugin;
201        }
202    }
203}