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    }