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}