001 package org.bukkit.command;
002
003 import static org.bukkit.util.Java15Compat.Arrays_copyOfRange;
004
005 import java.util.ArrayList;
006 import java.util.Collection;
007 import java.util.Collections;
008 import java.util.HashMap;
009 import java.util.Iterator;
010 import java.util.List;
011 import java.util.Map;
012 import java.util.regex.Pattern;
013
014 import org.apache.commons.lang.Validate;
015 import org.bukkit.Server;
016 import org.bukkit.command.defaults.*;
017 import org.bukkit.entity.Player;
018 import org.bukkit.util.StringUtil;
019
020 public class SimpleCommandMap implements CommandMap {
021 private static final Pattern PATTERN_ON_SPACE = Pattern.compile(" ", Pattern.LITERAL);
022 protected final Map<String, Command> knownCommands = new HashMap<String, Command>();
023 private final Server server;
024
025 public SimpleCommandMap(final Server server) {
026 this.server = server;
027 setDefaultCommands();
028 }
029
030 private void setDefaultCommands() {
031 register("bukkit", new SaveCommand());
032 register("bukkit", new SaveOnCommand());
033 register("bukkit", new SaveOffCommand());
034 register("bukkit", new StopCommand());
035 register("bukkit", new VersionCommand("version"));
036 register("bukkit", new ReloadCommand("reload"));
037 register("bukkit", new PluginsCommand("plugins"));
038 register("bukkit", new TimingsCommand("timings"));
039 }
040
041 public void setFallbackCommands() {
042 register("bukkit", new ListCommand());
043 register("bukkit", new OpCommand());
044 register("bukkit", new DeopCommand());
045 register("bukkit", new BanIpCommand());
046 register("bukkit", new PardonIpCommand());
047 register("bukkit", new BanCommand());
048 register("bukkit", new PardonCommand());
049 register("bukkit", new KickCommand());
050 register("bukkit", new TeleportCommand());
051 register("bukkit", new GiveCommand());
052 register("bukkit", new TimeCommand());
053 register("bukkit", new SayCommand());
054 register("bukkit", new WhitelistCommand());
055 register("bukkit", new TellCommand());
056 register("bukkit", new MeCommand());
057 register("bukkit", new KillCommand());
058 register("bukkit", new GameModeCommand());
059 register("bukkit", new HelpCommand());
060 register("bukkit", new ExpCommand());
061 register("bukkit", new ToggleDownfallCommand());
062 register("bukkit", new BanListCommand());
063 register("bukkit", new DefaultGameModeCommand());
064 register("bukkit", new SeedCommand());
065 register("bukkit", new DifficultyCommand());
066 register("bukkit", new WeatherCommand());
067 register("bukkit", new SpawnpointCommand());
068 register("bukkit", new ClearCommand());
069 register("bukkit", new GameRuleCommand());
070 register("bukkit", new EnchantCommand());
071 register("bukkit", new TestForCommand());
072 register("bukkit", new EffectCommand());
073 register("bukkit", new ScoreboardCommand());
074 register("bukkit", new PlaySoundCommand());
075 register("bukkit", new SpreadPlayersCommand());
076 register("bukkit", new SetWorldSpawnCommand());
077 register("bukkit", new SetIdleTimeoutCommand());
078 register("bukkit", new AchievementCommand());
079 }
080
081 /**
082 * {@inheritDoc}
083 */
084 public void registerAll(String fallbackPrefix, List<Command> commands) {
085 if (commands != null) {
086 for (Command c : commands) {
087 register(fallbackPrefix, c);
088 }
089 }
090 }
091
092 /**
093 * {@inheritDoc}
094 */
095 public boolean register(String fallbackPrefix, Command command) {
096 return register(command.getName(), fallbackPrefix, command);
097 }
098
099 /**
100 * {@inheritDoc}
101 */
102 public boolean register(String label, String fallbackPrefix, Command command) {
103 label = label.toLowerCase().trim();
104 fallbackPrefix = fallbackPrefix.toLowerCase().trim();
105 boolean registered = register(label, command, false, fallbackPrefix);
106
107 Iterator<String> iterator = command.getAliases().iterator();
108 while (iterator.hasNext()) {
109 if (!register(iterator.next(), command, true, fallbackPrefix)) {
110 iterator.remove();
111 }
112 }
113
114 // If we failed to register under the real name, we need to set the command label to the direct address
115 if (!registered) {
116 command.setLabel(fallbackPrefix + ":" + label);
117 }
118
119 // Register to us so further updates of the commands label and aliases are postponed until its reregistered
120 command.register(this);
121
122 return registered;
123 }
124
125 /**
126 * Registers a command with the given name is possible. Also uses
127 * fallbackPrefix to create a unique name.
128 *
129 * @param label the name of the command, without the '/'-prefix.
130 * @param command the command to register
131 * @param isAlias whether the command is an alias
132 * @param fallbackPrefix a prefix which is prepended to the command for a
133 * unique address
134 * @return true if command was registered, false otherwise.
135 */
136 private synchronized boolean register(String label, Command command, boolean isAlias, String fallbackPrefix) {
137 knownCommands.put(fallbackPrefix + ":" + label, command);
138 if ((command instanceof VanillaCommand || isAlias) && knownCommands.containsKey(label)) {
139 // Request is for an alias/fallback command and it conflicts with
140 // a existing command or previous alias ignore it
141 // Note: This will mean it gets removed from the commands list of active aliases
142 return false;
143 }
144
145 boolean registered = true;
146
147 // If the command exists but is an alias we overwrite it, otherwise we return
148 Command conflict = knownCommands.get(label);
149 if (conflict != null && conflict.getLabel().equals(label)) {
150 return false;
151 }
152
153 if (!isAlias) {
154 command.setLabel(label);
155 }
156 knownCommands.put(label, command);
157
158 return registered;
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 public boolean dispatch(CommandSender sender, String commandLine) throws CommandException {
165 String[] args = PATTERN_ON_SPACE.split(commandLine);
166
167 if (args.length == 0) {
168 return false;
169 }
170
171 String sentCommandLabel = args[0].toLowerCase();
172 Command target = getCommand(sentCommandLabel);
173
174 if (target == null) {
175 return false;
176 }
177
178 try {
179 // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false)
180 target.execute(sender, sentCommandLabel, Arrays_copyOfRange(args, 1, args.length));
181 } catch (CommandException ex) {
182 throw ex;
183 } catch (Throwable ex) {
184 throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex);
185 }
186
187 // return true as command was handled
188 return true;
189 }
190
191 public synchronized void clearCommands() {
192 for (Map.Entry<String, Command> entry : knownCommands.entrySet()) {
193 entry.getValue().unregister(this);
194 }
195 knownCommands.clear();
196 setDefaultCommands();
197 }
198
199 public Command getCommand(String name) {
200 Command target = knownCommands.get(name.toLowerCase());
201 return target;
202 }
203
204 public List<String> tabComplete(CommandSender sender, String cmdLine) {
205 Validate.notNull(sender, "Sender cannot be null");
206 Validate.notNull(cmdLine, "Command line cannot null");
207
208 int spaceIndex = cmdLine.indexOf(' ');
209
210 if (spaceIndex == -1) {
211 ArrayList<String> completions = new ArrayList<String>();
212 Map<String, Command> knownCommands = this.knownCommands;
213
214 final String prefix = (sender instanceof Player ? "/" : "");
215
216 for (Map.Entry<String, Command> commandEntry : knownCommands.entrySet()) {
217 Command command = commandEntry.getValue();
218
219 if (!command.testPermissionSilent(sender)) {
220 continue;
221 }
222
223 String name = commandEntry.getKey(); // Use the alias, not command name
224
225 if (StringUtil.startsWithIgnoreCase(name, cmdLine)) {
226 completions.add(prefix + name);
227 }
228 }
229
230 Collections.sort(completions, String.CASE_INSENSITIVE_ORDER);
231 return completions;
232 }
233
234 String commandName = cmdLine.substring(0, spaceIndex);
235 Command target = getCommand(commandName);
236
237 if (target == null) {
238 return null;
239 }
240
241 if (!target.testPermissionSilent(sender)) {
242 return null;
243 }
244
245 String argLine = cmdLine.substring(spaceIndex + 1, cmdLine.length());
246 String[] args = PATTERN_ON_SPACE.split(argLine, -1);
247
248 try {
249 return target.tabComplete(sender, commandName, args);
250 } catch (CommandException ex) {
251 throw ex;
252 } catch (Throwable ex) {
253 throw new CommandException("Unhandled exception executing tab-completer for '" + cmdLine + "' in " + target, ex);
254 }
255 }
256
257 public Collection<Command> getCommands() {
258 return Collections.unmodifiableCollection(knownCommands.values());
259 }
260
261 public void registerServerAliases() {
262 Map<String, String[]> values = server.getCommandAliases();
263
264 for (String alias : values.keySet()) {
265 if (alias.contains(":") || alias.contains(" ")) {
266 server.getLogger().warning("Could not register alias " + alias + " because it contains illegal characters");
267 continue;
268 }
269
270 String[] commandStrings = values.get(alias);
271 List<String> targets = new ArrayList<String>();
272 StringBuilder bad = new StringBuilder();
273
274 for (String commandString : commandStrings) {
275 String[] commandArgs = commandString.split(" ");
276 Command command = getCommand(commandArgs[0]);
277
278 if (command == null) {
279 if (bad.length() > 0) {
280 bad.append(", ");
281 }
282 bad.append(commandString);
283 } else {
284 targets.add(commandString);
285 }
286 }
287
288 if (bad.length() > 0) {
289 server.getLogger().warning("Could not register alias " + alias + " because it contains commands that do not exist: " + bad);
290 continue;
291 }
292
293 // We register these as commands so they have absolute priority.
294 if (targets.size() > 0) {
295 knownCommands.put(alias.toLowerCase(), new FormattedCommandAlias(alias.toLowerCase(), targets.toArray(new String[targets.size()])));
296 } else {
297 knownCommands.remove(alias.toLowerCase());
298 }
299 }
300 }
301 }