diff --git a/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/launch/NovaCommands.java b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/launch/NovaCommands.java new file mode 100644 index 0000000..ab3dffe --- /dev/null +++ b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/launch/NovaCommands.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.wrapper.mc.forge.v1_7_10.launch; + +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import net.minecraft.command.CommandHandler; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandManager; +import net.minecraft.server.MinecraftServer; +import nova.commands.CommandManager; +import nova.commands.wrapper.mc.forge.v1_7_10.wrapper.commands.CommandConverter; +import nova.core.event.ServerEvent; +import nova.core.event.bus.GlobalEvents; +import nova.core.loader.Mod; +import nova.core.nativewrapper.NativeManager; +import nova.core.wrapper.mc.forge.v17.launcher.ForgeLoadable; +import nova.internal.commands.depmodules.CommandsModule; + +/** + * @author ExE Boss + */ +@Mod(id = "nova-commands-wrapper", name = "NOVA Commands Wrapper", version = "0.0.1", novaVersion = "0.1.0", modules = { CommandsModule.class }) +public class NovaCommands implements ForgeLoadable { + + private final NativeManager natives; + private final CommandManager commands; + private final GlobalEvents events; + + private final CommandConverter converter; + + public NovaCommands(CommandManager commandManager, GlobalEvents events, NativeManager natives) { + this.commands = commandManager; + this.events = events; + this.natives = natives; + + this.converter = new CommandConverter(); + this.natives.registerConverter(this.converter); + + this.events.on(ServerEvent.Start.class).bind(this::serverStarting); + } + + @Override + public void preInit(FMLPreInitializationEvent evt) { + this.commands.init(); + } + + @SuppressWarnings("unchecked") + public void serverStarting(ServerEvent.Start evt) { + ICommandManager mcManager = MinecraftServer.getServer().getCommandManager(); + mcManager.getCommands().values().stream().filter(cmd -> cmd instanceof ICommand).forEach(cmd -> this.converter.registerMinecraftCommand((ICommand) cmd)); + if (mcManager instanceof CommandHandler) { + this.commands.registry.forEach(command -> ((CommandHandler) mcManager).registerCommand(this.natives.toNative(command))); + } + } +} diff --git a/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/CommandConverter.java b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/CommandConverter.java new file mode 100644 index 0000000..73c0281 --- /dev/null +++ b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/CommandConverter.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.wrapper.mc.forge.v1_7_10.wrapper.commands; + +import com.google.common.collect.HashBiMap; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import net.minecraft.command.ICommand; +import nova.commands.Command; +import nova.commands.event.CommandEvent; +import nova.commands.wrapper.mc.forge.v1_7_10.wrapper.commands.backward.BWCommand; +import nova.commands.wrapper.mc.forge.v1_7_10.wrapper.commands.forward.FWCommand; +import nova.core.nativewrapper.NativeConverter; +import nova.core.wrapper.mc.forge.v17.launcher.ForgeLoadable; +import nova.internal.core.Game; + +/** + * @author ExE Boss + */ +public class CommandConverter implements NativeConverter, ForgeLoadable { + + /** + * A map of all items registered + */ + private final HashBiMap map = HashBiMap.create(); + + @Override + public Class getNovaSide() { + return Command.class; + } + + @Override + public Class getNativeSide() { + return ICommand.class; + } + + @Override + public Command toNova(ICommand command) { + if (command == null) { + return null; + } + + if (command instanceof FWCommand) { + return ((FWCommand) command).wrapped; + } else { + if (map.containsValue(command)) { + return map.inverse().get(command); + } else { + BWCommand wrapper = new BWCommand(command); + map.put(wrapper, command); + return wrapper; + } + } + } + + @Override + public ICommand toNative(Command command) { + if (command == null) { + return null; + } + + if (command instanceof BWCommand) { + return ((BWCommand) command).wrapped; + } else { + if (map.containsKey(command)) { + return map.get(command); + } else { + FWCommand wrapper = new FWCommand(command); + map.put(command, wrapper); + return wrapper; + } + } + } + + @Override + public void preInit(FMLPreInitializationEvent event) { + //NOTE: There should NEVER be command already registered in preInit() stage of a NativeConverter. + Game.events().on(CommandEvent.Register.class).bind(evt -> registerNovaCommand(evt.command)); + } + + public void registerMinecraftCommand(ICommand command) { + if (map.containsValue(command)) + return; + + BWCommand wrapper = new BWCommand(command); + map.put(wrapper, command); + } + + private void registerNovaCommand(Command command) { + if (map.containsKey(command)) + return; + + FWCommand wrapper = new FWCommand(command); + map.put(command, wrapper); + } +} diff --git a/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/backward/BWCommand.java b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/backward/BWCommand.java new file mode 100644 index 0000000..12f6b13 --- /dev/null +++ b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/backward/BWCommand.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.wrapper.mc.forge.v1_7_10.wrapper.commands.backward; + +import net.minecraft.command.ICommand; +import nova.commands.Command; +import nova.commands.exception.CommandException; +import nova.core.entity.component.Player; +import nova.core.wrapper.mc.forge.v17.util.WrapUtility; +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; + +import java.util.List; +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class BWCommand extends Command { + + public final ICommand wrapped; + + public BWCommand(ICommand wrapped) { + super(wrapped.getCommandName()); + this.wrapped = wrapped; + } + + @Override + public void handle(Optional player, boolean prejoined, String... args) throws CommandException { + try { + this.wrapped.processCommand(WrapUtility.getMCPlayer(player), args); + } catch (net.minecraft.command.CommandException ex) { + throw new CommandException(ex.getMessage(), ex, ex.getErrorOjbects()); + } + } + + @Override + @SuppressWarnings("unchecked") + public List getAutocompleteList(Optional player, boolean prejoined, String[] args, Optional pos) { + return this.wrapped.addTabCompletionOptions(WrapUtility.getMCPlayer(player), args); + } + + @Override + public String getCommandUsage(Optional player) { + return wrapped.getCommandUsage(WrapUtility.getMCPlayer(player)); + } +} diff --git a/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/forward/FWCommand.java b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/forward/FWCommand.java new file mode 100644 index 0000000..5412cf5 --- /dev/null +++ b/minecraft/1.7.10/src/main/java/nova/commands/wrapper/mc/forge/v1_7_10/wrapper/commands/forward/FWCommand.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands.wrapper.mc.forge.v1_7_10.wrapper.commands.forward; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; +import nova.commands.Command; +import nova.core.wrapper.mc.forge.v17.util.WrapUtility; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class FWCommand extends CommandBase { + + public final Command wrapped; + + public FWCommand(Command wrapped) { + this.wrapped = wrapped; + } + + @Override + public String getCommandName() { + return wrapped.getCommandName(); + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return wrapped.getCommandUsage(sender instanceof EntityPlayer ? WrapUtility.getNovaPlayer((EntityPlayer) sender) : Optional.empty()); + } + + @Override + public List getCommandAliases() { + return Collections.emptyList(); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + try { + this.wrapped.handle(sender instanceof EntityPlayer ? WrapUtility.getNovaPlayer((EntityPlayer) sender) : Optional.empty(), false, args); + } catch (nova.commands.exception.CommandException ex) { + throw (CommandException) new CommandException(ex.getMessage(), ex.getArguments().orElse(new Object[0])).initCause(ex); + } + } + + @Override + public List addTabCompletionOptions(ICommandSender sender, String[] args) { + return this.wrapped.getAutocompleteList(sender instanceof EntityPlayer ? WrapUtility.getNovaPlayer((EntityPlayer) sender) : Optional.empty(), false, args, Optional.empty()); + } + + @Override + public boolean canCommandSenderUseCommand(ICommandSender sender) { + return true; + } +} diff --git a/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/launch/NovaCommands.java b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/launch/NovaCommands.java new file mode 100644 index 0000000..094f23e --- /dev/null +++ b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/launch/NovaCommands.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.wrapper.mc.forge.v1_8.launch; + +import net.minecraft.command.CommandHandler; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandManager; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import nova.commands.CommandManager; +import nova.commands.wrapper.mc.forge.v1_8.wrapper.commands.CommandConverter; +import nova.core.event.ServerEvent; +import nova.core.event.bus.GlobalEvents; +import nova.core.loader.Mod; +import nova.core.nativewrapper.NativeManager; +import nova.core.wrapper.mc.forge.v18.launcher.ForgeLoadable; +import nova.internal.commands.depmodules.CommandsModule; + +/** + * @author ExE Boss + */ +@Mod(id = "nova-commands-wrapper", name = "NOVA Commands Wrapper", version = "0.0.1", novaVersion = "0.1.0", modules = { CommandsModule.class }) +public class NovaCommands implements ForgeLoadable { + + private final NativeManager natives; + private final CommandManager commands; + private final GlobalEvents events; + + private final CommandConverter converter; + + public NovaCommands(CommandManager commandManager, GlobalEvents events, NativeManager natives) { + this.commands = commandManager; + this.events = events; + this.natives = natives; + + this.converter = new CommandConverter(); + this.natives.registerConverter(this.converter); + this.events.on(ServerEvent.Start.class).bind(this::serverStarting); + } + + @Override + public void preInit(FMLPreInitializationEvent evt) { + this.commands.init(); + } + + @SuppressWarnings("unchecked") + public void serverStarting(ServerEvent.Start evt) { + ICommandManager mcManager = MinecraftServer.getServer().getCommandManager(); + mcManager.getCommands().values().stream().filter(cmd -> cmd instanceof ICommand).forEach(cmd -> this.converter.registerMinecraftCommand((ICommand) cmd)); + if (mcManager instanceof CommandHandler) { + this.commands.registry.forEach(command -> ((CommandHandler) mcManager).registerCommand(this.natives.toNative(command))); + } + } +} diff --git a/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/CommandConverter.java b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/CommandConverter.java new file mode 100644 index 0000000..63f748f --- /dev/null +++ b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/CommandConverter.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.wrapper.mc.forge.v1_8.wrapper.commands; + +import com.google.common.collect.HashBiMap; +import net.minecraft.command.ICommand; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import nova.commands.Command; +import nova.commands.CommandManager; +import nova.commands.event.CommandEvent; +import nova.commands.wrapper.mc.forge.v1_8.wrapper.commands.backward.BWCommand; +import nova.commands.wrapper.mc.forge.v1_8.wrapper.commands.forward.FWCommand; +import nova.core.nativewrapper.NativeConverter; +import nova.core.wrapper.mc.forge.v18.launcher.ForgeLoadable; +import nova.internal.core.Game; + +/** + * @author ExE Boss + */ +public class CommandConverter implements NativeConverter, ForgeLoadable { + + /** + * A map of all commands registered + */ + private final HashBiMap map = HashBiMap.create(); + + @Override + public Class getNovaSide() { + return Command.class; + } + + @Override + public Class getNativeSide() { + return ICommand.class; + } + + @Override + public Command toNova(ICommand command) { + if (command == null) { + return null; + } + + if (command instanceof FWCommand) { + return ((FWCommand) command).wrapped; + } else { + if (map.containsValue(command)) { + return map.inverse().get(command); + } else { + BWCommand wrapper = new BWCommand(command); + map.put(wrapper, command); + return wrapper; + } + } + } + + @Override + public ICommand toNative(Command command) { + if (command == null) { + return null; + } + + if (command instanceof BWCommand) { + return ((BWCommand) command).wrapped; + } else { + if (map.containsKey(command)) { + return map.get(command); + } else { + FWCommand wrapper = new FWCommand(command); + map.put(command, wrapper); + return wrapper; + } + } + } + + @Override + public void preInit(FMLPreInitializationEvent event) { + //NOTE: There should NEVER be command already registered in preInit() stage of a NativeConverter. + Game.events().on(CommandEvent.Register.class).bind(evt -> registerNovaCommand(evt.command)); + } + + public void registerMinecraftCommand(ICommand command) { + if (map.containsValue(command)) + return; + + BWCommand wrapper = new BWCommand(command); + map.put(wrapper, command); + } + + private void registerNovaCommand(Command command) { + if (map.containsKey(command)) + return; + + FWCommand wrapper = new FWCommand(command); + map.put(command, wrapper); + } +} diff --git a/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/backward/BWCommand.java b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/backward/BWCommand.java new file mode 100644 index 0000000..ad7854d --- /dev/null +++ b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/backward/BWCommand.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.wrapper.mc.forge.v1_8.wrapper.commands.backward; + +import net.minecraft.command.ICommand; +import nova.commands.Command; +import nova.commands.exception.CommandException; +import nova.core.entity.component.Player; +import nova.core.wrapper.mc.forge.v18.util.WrapUtility; +import nova.internal.core.Game; +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; + +import java.util.List; +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class BWCommand extends Command { + + public final ICommand wrapped; + + public BWCommand(ICommand wrapped) { + super(wrapped.getCommandName()); + this.wrapped = wrapped; + } + + @Override + public void handle(Optional player, boolean prejoined, String... args) throws CommandException { + try { + this.wrapped.processCommand(WrapUtility.getMCPlayer(player), args); + } catch (net.minecraft.command.CommandException ex) { + throw new CommandException(ex.getMessage(), ex, ex.getErrorObjects()); + } + } + + @Override + @SuppressWarnings("unchecked") + public List getAutocompleteList(Optional player, boolean prejoined, String[] args, Optional pos) { + return this.wrapped.addTabCompletionOptions(WrapUtility.getMCPlayer(player), args, pos.isPresent() ? Game.natives().toNative(pos.get()) : null); + } + + @Override + public String getCommandUsage(Optional player) { + return wrapped.getCommandUsage(WrapUtility.getMCPlayer(player)); + } +} diff --git a/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/forward/FWCommand.java b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/forward/FWCommand.java new file mode 100644 index 0000000..8830bc9 --- /dev/null +++ b/minecraft/1.8/src/main/java/nova/commands/wrapper/mc/forge/v1_8/wrapper/commands/forward/FWCommand.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands.wrapper.mc.forge.v1_8.wrapper.commands.forward; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.BlockPos; +import nova.commands.ArgsParser; +import nova.commands.Command; +import nova.core.wrapper.mc.forge.v18.util.WrapUtility; +import nova.internal.core.Game; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class FWCommand extends CommandBase { + + public final Command wrapped; + + public FWCommand(Command wrapped) { + this.wrapped = wrapped; + } + + @Override + public String getCommandName() { + return wrapped.getCommandName(); + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return wrapped.getCommandUsage(sender instanceof EntityPlayer ? WrapUtility.getNovaPlayer((EntityPlayer) sender) : Optional.empty()); + } + + @Override + public List getCommandAliases() { + return Collections.emptyList(); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + try { + this.wrapped.handle(sender instanceof EntityPlayer ? WrapUtility.getNovaPlayer((EntityPlayer) sender) : Optional.empty(), false, args); + } catch (nova.commands.exception.CommandException ex) { + throw (CommandException) new CommandException(ex.getMessage(), ex.getArguments().orElse(new Object[0])).initCause(ex); + } + } + + @Override + public List addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) { + return this.wrapped.getAutocompleteList(sender instanceof EntityPlayer ? WrapUtility.getNovaPlayer((EntityPlayer) sender) : Optional.empty(), false, args, pos == null ? Optional.empty() : Optional.of(Game.natives().toNova(pos))); + } + + @Override + public boolean canCommandSenderUseCommand(ICommandSender sender) { + return true; + } +} diff --git a/src/main/java/nova/commands/Args.java b/src/main/java/nova/commands/Args.java new file mode 100644 index 0000000..dcbcc93 --- /dev/null +++ b/src/main/java/nova/commands/Args.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Spliterator; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A class containing arguments. + * + * @author ExE Boss, Victorious3 + */ +public final class Args implements Iterable { + public static final Args EMPTY_ARGS = new Args(); + + private final Object[] requiredArgs; + private final Map optionalArgsString; + private final Map optionalArgsChar; + + /** + * Constructs a new empty Args object. + */ + public Args() { + this.requiredArgs = new Object[0]; + this.optionalArgsString = Collections.emptyMap(); + this.optionalArgsChar = Collections.emptyMap(); + } + + /** + * Constructs a new Args object for the following indexed arguments. + * + * @param requiredArgs The required arguments. Requested using {@link #get(int)}. + */ + public Args(Object... requiredArgs) { + this.requiredArgs = Arrays.copyOf(requiredArgs, requiredArgs.length); + this.optionalArgsString = Collections.emptyMap(); + this.optionalArgsChar = Collections.emptyMap(); + } + + /** + * Constructs a new Args object for the following indexed and named arguments. + * + * @param requiredArgs The required arguments. Requested using {@link #get(int)}. + * @param optionalArgsString The optional arguments. Requested using {@link #get(String)}. + * @param optionalArgsChar The optional arguments. Requested using {@link #get(char)}. + */ + public Args(Object[] requiredArgs, Map optionalArgsString, Map optionalArgsChar) { + this.requiredArgs = Arrays.copyOf(requiredArgs, requiredArgs.length); + this.optionalArgsString = new HashMap<>(); + this.optionalArgsString.putAll(optionalArgsString); + this.optionalArgsChar = new HashMap<>(); + this.optionalArgsChar.putAll(optionalArgsChar); + } + + /** + * Get the argument at the specified index. + * + * @param The type. + * @param index The index. + * @return The argument. + */ + @SuppressWarnings("unchecked") + public T get(int index) { + return (T) requiredArgs[index]; + } + + /** + * Get the named argument at the specified index. + * + * @param The type. + * @param index The index. + * @return The argument. + */ + @SuppressWarnings("unchecked") + public Optional get(String index) { + return optionalArgsString.containsKey(index) ? Optional.of((T) optionalArgsString.get(index)) : Optional.empty(); + } + + /** + * Get the named argument at the specified index. + * + * @param The type. + * @param index The index. + * @return The argument. + */ + @SuppressWarnings("unchecked") + public Optional get(char index) { + return optionalArgsChar.containsKey(index) ? Optional.of((T) optionalArgsChar.get(index)) : Optional.empty(); + } + + /** + * Get the size of the indexed arguments. + * + * @return The size of the indexed arguments. + */ + public int size() { + return requiredArgs.length; + } + + /** + * Get an iterator over the indexed arguments. + * + * @return An iterator over the indexed arguments. + */ + @Override + public Iterator iterator() { + return new Iterator() { + int index = 0; + + @Override + public boolean hasNext() { + return index < requiredArgs.length; + } + + @Override + public Object next() { + return requiredArgs[index++]; + } + }; + } + + /** + * Get a spliterator over the indexed arguments. + * + * @return A spliterator over the indexed arguments. + */ + @Override + public Spliterator spliterator() { + return Arrays.spliterator(requiredArgs); + } + + /** + * Get a stream of the indexed arguments. + * + * @return A stream of the indexed arguments. + */ + public Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + /** + * Get a parallel stream of the indexed arguments. + * + * @return A stream of the indexed arguments. + */ + public Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + Arrays.deepHashCode(this.requiredArgs); + hash = 97 * hash + Objects.hashCode(this.optionalArgsString); + hash = 97 * hash + Objects.hashCode(this.optionalArgsChar); + return hash; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + return Objects.deepEquals(this.requiredArgs, ((Args) other).requiredArgs) && + Objects.deepEquals(this.optionalArgsString, ((Args) other).optionalArgsString) && + Objects.deepEquals(this.optionalArgsChar, ((Args) other).optionalArgsChar); + } + + /** + * Attempts to reproduce the original String. + * However due to the way things are converted, + * this will usually not produce the original arguments. + * + * @return The arguments as a String. + */ + @Override + public String toString() { + List str = stream().map(Objects::toString).collect(Collectors.toCollection(LinkedList::new)); + str.addAll(optionalArgsString.entrySet().stream() + .map(entry -> new StringBuilder("--").append(entry.getKey()).append('=').append(entry.getValue()).toString()) + .collect(Collectors.toCollection(LinkedList::new))); + str.addAll(optionalArgsChar.entrySet().stream() + .filter(entry -> !optionalArgsString.containsValue(entry.getValue())) + .map(entry -> new StringBuilder("-").append(entry.getKey()).append(' ').append(entry.getValue()).toString()) + .collect(Collectors.toCollection(LinkedList::new))); + return String.join(" ", str.toArray(new String[str.size()])); + } +} diff --git a/src/main/java/nova/commands/ArgsParser.java b/src/main/java/nova/commands/ArgsParser.java new file mode 100644 index 0000000..37e226d --- /dev/null +++ b/src/main/java/nova/commands/ArgsParser.java @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands; + +import nova.commands.exception.CommandParseException; +import nova.core.retention.Data; +import nova.core.util.EnumSelector; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * A parser for GNU-style arguments. + * + * @author ExE Boss, Victorious3 + */ +public final class ArgsParser { + + private static final Map>, Function> CONVERTERS = new HashMap<>(); + + private final String[] args; + private final List optional = new ArrayList<>(); + private final boolean prejoined; + private Class[] required = new Class[0]; + private Args parsed; + + /** + * Create an ArgParser for the following arguments. + * + * @param prejoined If strings in the {@code args} parameter can contain spaces and "/' should not be treated as delimiters. + * @param args The string arguments. + */ + public ArgsParser(boolean prejoined, String... args) { + this(prejoined, (Object[]) args); + } + + /** + * Create an ArgParser for the following arguments. + * + * @param prejoined If strings in the {@code args} parameter can contain spaces and "/' should not be treated as delimiters. + * @param args The string arguments. + */ + public ArgsParser(boolean prejoined, Object... args) { + this.args = Arrays.stream(args).map(Objects::toString).toArray(String[]::new); + this.prejoined = prejoined; + } + + /** + * Create an ArgParser for the following arguments. + * + * @param args The pre-joined string arguments (can contain spaces and "/' should not be treated as delimiters). + */ + public ArgsParser(String... args) { + this(false, args); + } + + /** + * Create an ArgParser for the following arguments. + * + * @param args The pre-joined string arguments (can contain spaces and "/' should not be treated as delimiters). + */ + public ArgsParser(Object... args) { + this(false, args); + } + + private void checkWritable() { + if (parsed != null) + throw new IllegalStateException("No edits are allowed after ArgParser has been locked."); + } + + /** + * Specify the required classes. + * + * @param required The required classes. + * @return this + */ + public ArgsParser args(Class... required) { + checkWritable(); + Arrays.stream(required).forEach(Objects::requireNonNull); + int i = this.required.length; + this.required = Arrays.copyOf(this.required, this.required.length + required.length); + System.arraycopy(required, 0, this.required, i, required.length); + return this; + } + + /** + * Specify an optional parameter. + * + * @param type The class of the argument type + * @param shortName The short name (-shortName), should be in lower-case. + * @param longName The long name (--longName), should be in lower-hyphen-case. + * @return this + */ + public ArgsParser opt(Class type, char shortName, String longName) { + checkWritable(); + optional.add(new Object[]{type, Character.toLowerCase(shortName), longName == null ? longName : longName.toLowerCase()}); + return this; + } + + /** + * Specify an optional parameter. + * + * @param type The class of the argument type + * @param longName The long name (--longName), should be in lower-hyphen-case. + * @return this + */ + public ArgsParser opt(Class type, String longName) { + return this.opt(type, '\u0000', longName); + } + + /** + * Specify an optional parameter. + * + * @param type The class of the argument type + * @param shortName The short name (-shortName) + * @return this + */ + public ArgsParser opt(Class type, char shortName) { + return this.opt(type, shortName, null); + } + + /** + * Specify an optional flag. + * + * @param shortName The short name (-shortName) + * @param longName The long name (--longName), should be in lower-hyphen-case. + * @return this + */ + public ArgsParser opt(char shortName, String longName) { + return this.opt(null, shortName, longName); + } + + /** + * Specify an optional flag. + * + * @param longName The long name (--longName), should be in lower-hyphen-case. + * @return this + */ + public ArgsParser opt(String longName) { + return this.opt('\u0000', longName); + } + + /** + * Specify an optional flag. + * + * @param shortName The short name (-shortName) + * @return this + */ + public ArgsParser opt(char shortName) { + return this.opt(shortName, null); + } + + /** + * Parse the supplied arguments. + * After this method is called, no more edits are allowed. + * + * @return The parsed {@link Args} instance. + * @throws CommandParseException The {@link #args(java.lang.Class...)} + * hasn't been called and the constructor arguments parameter isn't empty. + */ + public Args parse() throws CommandParseException { + if (parsed != null) + return parsed; + + if (args.length == 0) { // Args length is zero. It doesn't matter that we didn't specify required arguments. + parsed = Args.EMPTY_ARGS; + return parsed; + } + + if (required.length == 0) + throw new CommandParseException("Errors with command", Errors.UNSPECIFIED_REQUIRED_ARGUMENTS); + + int requiredPos = 0; + boolean continous = false; + boolean optional = false; + Delimiters continousDelimiter = null; + int curvyBracketsStack = 0; + StringBuilder arg = new StringBuilder(); + String currentOpt = null; + char currentOptC = '\u0000'; + Class currentClass = null; + Object[] required = new Object[this.required.length]; + Map optsChar = new HashMap<>(); + Map optsStr = new HashMap<>(); + + EnumSelector errored = EnumSelector.of(Errors.class).blockAll(); + + for (String stringArg : args) { + if (stringArg.isEmpty()) + continue; + + int substringIndexStart = 0; + int substringIndexEnd = 0; + if (!this.prejoined) { + if (stringArg.charAt(0) == '{') + curvyBracketsStack++; + + if (!continous) { + switch (stringArg.charAt(0)) { + case '\'': { + continous = true; + substringIndexStart = 1; + continousDelimiter = Delimiters.SINGLE_QUOTES; + break; + } case '"': { + continous = true; + substringIndexStart = 1; + continousDelimiter = Delimiters.DOUBLE_QUOTES; + break; + } case '{': { + continous = true; + curvyBracketsStack = 1; + substringIndexStart = 0; + continousDelimiter = Delimiters.CURVY_BRACKETS; + break; + } + } + } + + if (continous) { + // TODO: Better escaping + switch (stringArg.charAt(stringArg.length() - 1)) { + case '\'': { + if (continousDelimiter != Delimiters.SINGLE_QUOTES) + break; + continous = false; + substringIndexEnd = 1; + continousDelimiter = null; + break; + } case '"': { + if (continousDelimiter != Delimiters.DOUBLE_QUOTES) + break; + continous = false; + substringIndexEnd = 1; + continousDelimiter = null; + break; + } case '}': { + curvyBracketsStack--; + if (continousDelimiter != Delimiters.CURVY_BRACKETS || curvyBracketsStack != 0) + break; + continous = false; + substringIndexEnd = 0; + continousDelimiter = null; + break; + } + } + + if (!optional && !continous) { + if (arg.length() > 0) arg.append(' '); + arg.append(stringArg.substring(substringIndexStart, stringArg.length() - substringIndexEnd)); + optional = false; + } + } + } + + if (optional || continous) { + if (arg.length() > 0) arg.append(' '); + arg.append(stringArg.substring(substringIndexStart, stringArg.length() - substringIndexEnd)); + if (!continous) optional = false; + } else if (stringArg.startsWith("-")) { + // Argument is optional + if (stringArg.startsWith("--")) { + currentOpt = stringArg.substring(2); + final String optName = currentOpt.substring(0, currentOpt.contains("=") ? currentOpt.indexOf('=') : currentOpt.length()); + Optional dataOp = this.optional.stream().filter(ary -> optName.equalsIgnoreCase((String)ary[2])).findFirst(); + if (!dataOp.isPresent()) { + errored.apart(Errors.UNKNOWN_OPTIONAL); + continue; + } + arg.append(currentOpt.contains("=") ? currentOpt.substring(currentOpt.indexOf('=') + 1) : ""); + Object[] data = dataOp.get(); + if (data[1] != null) currentOptC = (char)data[1]; + if (data[0] != null) { + currentClass = (Class) data[0]; + if (arg.length() == 0) optional = true; + } else { + currentClass = boolean.class; + arg.append(true); + } + if (arg.length() > 0 && !this.prejoined && (arg.charAt(0) == '"' || arg.charAt(0) == '\'') && !continous) { + continous = true; + } + } else { + final String optName = stringArg.substring(1); + Optional dataOp = this.optional.stream().filter(ary -> optName.equalsIgnoreCase(new String(new char[]{(char)ary[1]}))).findFirst(); + if (!dataOp.isPresent()) { + errored.apart(Errors.UNKNOWN_OPTIONAL); + continue; + } + currentOptC = optName.charAt(0); + Object[] data = dataOp.get(); + if (data[2] != null) currentOpt = (String)data[2]; + if (data[0] != null) { + currentClass = (Class) data[0]; + optional = true; + } else { + currentClass = boolean.class; + arg.append(true); + } + } + } else { + if (requiredPos >= this.required.length) { + requiredPos++; + errored.apart(Errors.TOO_LONG); // TODO: Maybe ignore this + continue; + } + Class requiredClass = this.required[requiredPos]; + if (arg.length() > 0) { + required[requiredPos] = toArg(requiredClass, arg.toString()); + } else { + required[requiredPos] = toArg(requiredClass, stringArg); + } + requiredPos++; + } + if (!optional && !continous && currentClass != null && arg.length() > 0) { + Object argObj = toArg(currentClass, arg.toString()); + if (currentOpt != null) optsStr.put(currentOpt, argObj); + if (currentOptC != '\u0000') optsChar.put(currentOptC, argObj); + + currentOpt = null; + currentOptC = '\u0000'; + currentClass = null; + + arg.delete(0, arg.length()); + } + } + + if (continous) + errored.apart(Errors.UNENDED_QUOTES); + if (curvyBracketsStack > 0) + errored.apart(Errors.UNENDED_CURVY_BRACKETS); + + errored.lock(); + + if (!errored.blocksAll()) + throw new CommandParseException("Errors with command", errored); + + parsed = new Args(required, optsStr, optsChar); + return parsed; + } + + @SuppressWarnings({"unchecked", "UnnecessaryBoxing"}) + private static T toArg(final Class clazz, final String str) { + if (Number.class.isAssignableFrom(clazz) || (clazz.isPrimitive() && clazz != boolean.class && clazz != char.class)) { + if (clazz == Byte.class || clazz == byte.class) + return (T) Byte.valueOf(str); + if (clazz == Short.class || clazz == short.class) + return (T) Short.valueOf(str); + if (clazz == Integer.class || clazz == int.class) { + try { + return (T) Integer.valueOf(str); + } catch (NumberFormatException ex) { + return (T) Integer.valueOf(Integer.parseUnsignedInt(str)); + } + } if (clazz == Long.class || clazz == long.class) { + try { + return (T) Long.valueOf(str); + } catch (NumberFormatException ex) { + return (T) Long.valueOf(Long.parseUnsignedLong(str)); + } + } + + if (clazz == Float.class || clazz == float.class) + return (T) Float.valueOf(str); + if (clazz == Double.class || clazz == double.class) + return (T) Double.valueOf(str); + + if (clazz == BigInteger.class) + return (T) new BigInteger(str); + if (clazz == BigDecimal.class) + return (T) new BigDecimal(str); + } else { + if (clazz == String.class) + return (T) str; + if (clazz == Character.class || clazz == char.class) { + if (str.isEmpty() || str.length() > 1) throw new IllegalArgumentException("Argument \"" + str + "\" is too " + (str.isEmpty() ? "short" : "long") + ", it must be exactly 1 character long."); + return (T) Character.valueOf(str.charAt(0)); + } if (clazz == Boolean.class || clazz == boolean.class) + return (T) Boolean.valueOf(str); + if (clazz == Data.class) + return (T) Data.fromJSON(str); + } + + Optional> converter = CONVERTERS.entrySet().stream().filter(entry -> entry.getKey().test(clazz)).map(Entry::getValue).findFirst(); + if (converter.isPresent()) + return (T) converter.get().apply(str); + + throw new IllegalArgumentException("Object type " + clazz.getSimpleName() + " is not supported"); + } + + /** + * Register an argument converter. + * + * @param The return type. + * @param predicate The checker ({@code clazz -> clazz == SomeClass.class}). + * @param converter The converter ({@code SomeClass::parseString}) or something similar. + */ + public static void registerArgConverter(Predicate> predicate, Function converter) { + CONVERTERS.put(Objects.requireNonNull(predicate), Objects.requireNonNull(converter)); + } + + /** + * Check if the ArgsParser instance has been locked. + * + * @return The locked status. + */ + public boolean locked() { + return parsed != null; + } + + public static enum Errors { + PARSER_IS_LOCKED("Parser is locked"), + UNSPECIFIED_REQUIRED_ARGUMENTS("Required arguments unspecified"), + TOO_LONG("Arguments too long"), + UNENDED_QUOTES("Unended quotes"), + UNENDED_CURVY_BRACKETS("Unended curvy brackets"), + UNKNOWN_OPTIONAL("Unknown optional argument"); + + private final String displayName; + + private Errors(String displayName) { + this.displayName = displayName; + } + + @Override + public String toString() { + return this.displayName; + } + } + + private static enum Delimiters { + SINGLE_QUOTES, + DOUBLE_QUOTES, + CURVY_BRACKETS + } +} diff --git a/src/main/java/nova/commands/Command.java b/src/main/java/nova/commands/Command.java new file mode 100644 index 0000000..dbfe20b --- /dev/null +++ b/src/main/java/nova/commands/Command.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands; + +import nova.commands.exception.CommandException; +import nova.core.entity.component.Player; +import nova.core.event.bus.Event; +import nova.core.event.bus.EventBus; +import nova.core.util.Identifiable; +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author ExE Boss + */ +public abstract class Command implements Identifiable { + + protected final String command; + public final EventBus events = new EventBus<>(); + + /** + * Constructs a new command instance. + * + * @param command The command name. + */ + public Command(String command) { + this.command = command; + } + + /** + * Get the usage of the command. + * + * Example: {@code command [--optional] [-o]} + * + * @param player The player who issued this command. + * + * @return The usage of the command. + */ + public abstract String getCommandUsage(Optional player); + + /** + * Handle the command. + * + * @param player The player who issued this command. + * Won't be present if the command was issued from the server command line. + * @param prejoined If strings in the {@code args} parameter contains spaces and "/' should not be treated as delimiters. + * Useful if using {@link ArgsParser}. + * @param args The arguments that were given by the player. + * + * @throws CommandException If something goes wrong during command execution. + */ + public abstract void handle(Optional player, boolean prejoined, String... args) throws CommandException; + + /** + * Get a list of possible ways the command could auto-complete. + * + * @param player The player who issued this command. + * Won't be present if the command was issued from the server command line. + * @param args The already completed arguments, with the + * on at index {@code args.length - 1} being the incomplete one. + * @param prejoined If strings in the {@code args} parameter contains spaces and "/' should not be treated as delimiters. + * Useful if using {@link ArgsParser}. + * @param pos The position of the block that the player is looking at, if any. + * @return The list. Must not be null. + */ + public List getAutocompleteList(Optional player, boolean prejoined, String[] args, Optional pos) { + return Collections.emptyList(); + } + + /** + * Returns the command name. + * + * @return The command name. + */ + public final String getCommandName() { + return command; + } + + /** + * {@inheritDoc} + * + * @return The ID for a command is the command name in lowercase. + */ + @Override + public final String getID() { + return command.toLowerCase(); + } +} diff --git a/src/main/java/nova/commands/CommandManager.java b/src/main/java/nova/commands/CommandManager.java new file mode 100644 index 0000000..9698140 --- /dev/null +++ b/src/main/java/nova/commands/CommandManager.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands; + +import java.util.Optional; + +import nova.commands.event.CommandEvent; +import nova.core.util.registry.Manager; +import nova.core.util.registry.Manager.ManagerEvent; +import nova.core.util.registry.Registry; +import nova.internal.core.Game; + +/** + * @author ExE Boss + */ +public final class CommandManager extends Manager { + + public final Registry registry; + + public CommandManager() { + this.registry = new Registry<>(); + } + + /** + * Register a command. + * + * @param command The command to register. + * @return The registered command. (May be different) + */ + public Command register(Command command) { + CommandEvent.Register event = new CommandEvent.Register(command); + Game.events().publish(event); + registry.register(event.command); + return event.command; + } + + /** + * Get a registered command. + * + * @param command The command name/ID + * @return The command or an empty Optional, if it isn't registered. + */ + public Optional get(String command) { + return registry.get(command.toLowerCase()); + } + + @Override + public void init() { + Game.events().publish(new Init(this)); + } + + /** + * The CommandManger initialization event. + * + * Event is triggered when a CommandManager is initialized. + */ + public class Init extends ManagerEvent { + public Init(CommandManager manager) { + super(manager); + } + } +} diff --git a/src/main/java/nova/commands/event/CommandEvent.java b/src/main/java/nova/commands/event/CommandEvent.java new file mode 100644 index 0000000..da042c8 --- /dev/null +++ b/src/main/java/nova/commands/event/CommandEvent.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.event; + +import nova.commands.Args; +import nova.commands.Command; +import nova.core.entity.component.Player; +import nova.core.event.bus.CancelableEvent; + +import java.util.Optional; + +/** + * All events related to the command. + * + * @author ExE Boss + */ +public abstract class CommandEvent extends CancelableEvent { + + public final Command command; + + public CommandEvent(Command command) { + this.command = command; + } + + /** + * Event is triggered when a something happens during command execution. + */ + public static abstract class CommandHandleEvent extends CommandEvent { + public final Optional player; + public final Optional argsObj; + public final boolean prejoined; + public final String[] args; + + public CommandHandleEvent(Optional player, Command command, Args argsObj, boolean prejoined, String... args) { + super(command); + this.player = player; + this.argsObj = Optional.of(argsObj); + this.prejoined = prejoined; + this.args = args; + } + + public CommandHandleEvent(Optional player, Command command, boolean prejoined, String... args) { + super(command); + this.player = player; + this.argsObj = Optional.empty(); + this.prejoined = prejoined; + this.args = args; + } + } + + /** + * Event is triggered when a Command is registered. + * + * @see Command + * @see nova.commands.CommandManager#register(nova.commands.Command) + */ + public static class Register extends CancelableEvent { + public Command command; + + public Register(Command command) { + this.command = command; + } + } +} diff --git a/src/main/java/nova/commands/exception/CommandException.java b/src/main/java/nova/commands/exception/CommandException.java new file mode 100644 index 0000000..9a2a6aa --- /dev/null +++ b/src/main/java/nova/commands/exception/CommandException.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.exception; + +import nova.core.util.exception.NovaException; + +import java.util.Arrays; +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class CommandException extends NovaException { + + private static final long serialVersionUID = 1L; + private final Object[] arguments; + + public CommandException() { + this.arguments = null; + } + + public CommandException(String message, Object... parameters) { + super(message, parameters); + this.arguments = Arrays.copyOf(parameters, parameters.length); + } + + public CommandException(String message) { + super(message); + this.arguments = null; + } + + public CommandException(String message, Throwable cause) { + super(message, cause); + this.arguments = null; + } + + public CommandException(String message, Throwable cause, Object... parameters) { + super(message, parameters); + this.initCause(cause); + this.arguments = Arrays.copyOf(parameters, parameters.length); + } + + public CommandException(Throwable cause) { + super(cause); + this.arguments = null; + } + + public Optional getArguments() { + return Optional.ofNullable(Arrays.copyOf(arguments, arguments.length)); + } +} diff --git a/src/main/java/nova/commands/exception/CommandParseException.java b/src/main/java/nova/commands/exception/CommandParseException.java new file mode 100644 index 0000000..a413d7c --- /dev/null +++ b/src/main/java/nova/commands/exception/CommandParseException.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands.exception; + +import nova.commands.ArgsParser; +import nova.core.util.EnumSelector; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; + +/** + * @author ExE Boss + */ +public class CommandParseException extends CommandException { + + private static final long serialVersionUID = 1L; + + private final Set errors; + + public CommandParseException(String message) { + super(message); + this.errors = null; + } + + public CommandParseException(String message, Throwable cause) { + super(message, cause); + this.errors = null; + } + + private CommandParseException(String message, Set errors) { + super(message + ": " + errors); + this.errors = errors; + } + + public CommandParseException(String message, ArgsParser.Errors... errors) { + this(message, toEnumSet(errors)); + } + + public CommandParseException(String message, EnumSet errors) { + this(message, Collections.unmodifiableSet(errors)); + } + + public CommandParseException(String message, EnumSelector errors) { + this(message, errors.toSet()); + } + + public Optional> getErrors() { + return Optional.ofNullable(errors); + } + + private static EnumSet toEnumSet(ArgsParser.Errors... errors) { + EnumSet set = EnumSet.noneOf(ArgsParser.Errors.class); + set.addAll(Arrays.asList(errors)); + return set; + } +} diff --git a/src/main/java/nova/internal/commands/depmodules/CommandsModule.java b/src/main/java/nova/internal/commands/depmodules/CommandsModule.java new file mode 100644 index 0000000..5655399 --- /dev/null +++ b/src/main/java/nova/internal/commands/depmodules/CommandsModule.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.internal.commands.depmodules; + +import nova.commands.CommandManager; +import se.jbee.inject.bind.BinderModule; +import se.jbee.inject.util.Scoped; + +/** + * @author ExE Boss + */ +public class CommandsModule extends BinderModule { + + @Override + protected void declare() { + per(Scoped.APPLICATION).bind(CommandManager.class).toConstructor(); + } +} diff --git a/src/test/java/nova/commands/ArgsParserTest.java b/src/test/java/nova/commands/ArgsParserTest.java new file mode 100644 index 0000000..f8a2a5f --- /dev/null +++ b/src/test/java/nova/commands/ArgsParserTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands; + +import nova.core.retention.Data; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Iterator; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author ExE Boss + */ +public class ArgsParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testArgsParser() { + ArgsParser parser1 = new ArgsParser("String", true, 1, 2, 3, 4, 5.0, 6.0, "-a", "--fun") + .args(String.class, boolean.class, byte.class, short.class, int.class, long.class, float.class, double.class) + .opt('a').opt("fun"); + ArgsParser parser2 = new ArgsParser("String").args(String.class); + ArgsParser parser3 = new ArgsParser(false, new Object[]{"String", "--other", "\"a" + "b\"", "-x", "y"}) + .args(String.class).opt(String.class, "other").opt(char.class, 'x'); + ArgsParser parser4 = new ArgsParser('c', Integer.MAX_VALUE * 2L, BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.valueOf(2)), BigInteger.ONE, BigDecimal.TEN) + .args(char.class, int.class, long.class, BigInteger.class, BigDecimal.class); + ArgsParser parser5 = new ArgsParser(false, new Object[]{"{\"Key\":", "\"Value\"}"}).args(Data.class); + + assertThat(parser1.parse()).isNotNull(); + assertThat(parser2.parse()).isEqualTo(new Args("String")); + assertThat(parser3.parse()).isNotNull(); + assertThat(parser4.parse()).isNotNull(); + assertThat(parser5.parse()).isNotNull(); + + assertThat(parser1.locked()).isTrue(); + + assertThat((String)parser2.parse().get(0)).isEqualTo("String"); + + assertThat(parser1.parse().get('a')).isPresent(); + assertThat(parser1.parse().get('a').get()).isEqualTo(true); + + assertThat(parser1.parse().get("fun")).isPresent(); + assertThat(parser1.parse().get("fun").get()).isEqualTo(true); + + assertThat(parser2.parse().get("a")).isEmpty(); + assertThat(parser2.parse().get('a')).isEmpty(); + + Iterator iterator = parser2.parse().iterator(); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo("String"); + assertThat(iterator.hasNext()).isFalse(); + } + + @Test + public void testCannotWrite() { + ArgsParser parser = new ArgsParser("String").args(String.class); + parser.parse(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("No edits are allowed after ArgParser has been locked."); + parser.args(String.class); + } +} diff --git a/src/test/java/nova/commands/ArgsTest.java b/src/test/java/nova/commands/ArgsTest.java new file mode 100644 index 0000000..eab957f --- /dev/null +++ b/src/test/java/nova/commands/ArgsTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands; + +import org.junit.Before; +import org.junit.Test; + +import static nova.testutils.NovaAssertions.assertThat; + +/** + * Used to test {@link Args} + * + * @author ExE Boss + */ +public class ArgsTest { + + public Args args0; + public Args args1; + public Args args2; + public Args args3; + public Args args4; + + @Before + public void setUp() { + this.args0 = new ArgsParser().args().parse(); + this.args1 = new ArgsParser("Arg1").args(String.class).parse(); + this.args2 = new ArgsParser("Arg1", "Arg2").args(String.class, String.class).parse(); + this.args3 = new ArgsParser("Arg1", "Arg2", Math.PI).args(String.class, String.class, double.class).parse(); + this.args4 = new ArgsParser("Arg1", "Arg2", Math.PI, Math.E).args(String.class, String.class, double.class, double.class).parse(); + } + + @Test + public void testSize() { + assertThat(this.args0.size()).isZero(); + assertThat(this.args1.size()).isEqualTo(1); + assertThat(this.args2.size()).isEqualTo(2); + assertThat(this.args3.size()).isEqualTo(3); + assertThat(this.args4.size()).isEqualTo(4); + } + + @Test + public void testIterable() { + assertThat(this.args0.iterator().hasNext()).isFalse(); + assertThat(this.args1.iterator().hasNext()).isTrue(); + assertThat(this.args2.spliterator().getExactSizeIfKnown()).isEqualTo(2); + assertThat(this.args3.stream().count()).isEqualTo(3); + assertThat(this.args4.parallelStream().count()).isEqualTo(4); + } + + @Test + public void testEquals() { + assertThat(args0).isEqualTo(args0); + assertThat(args2).isNotEqualTo(args3); + assertThat(args1).isEqualTo(new ArgsParser("Arg1").args(String.class).parse()); + assertThat(args4).isNotEqualTo(null); + assertThat(args4).isNotEqualTo(this); + } + + @Test + public void testHashCode() { + assertThat(args0.hashCode()).isEqualTo(new ArgsParser().args().parse().hashCode()); + assertThat(args1.hashCode()).isEqualTo(new ArgsParser("Arg1").args(String.class).parse().hashCode()); + assertThat(args2.hashCode()).isEqualTo(new ArgsParser("Arg1", "Arg2").args(String.class, String.class).parse().hashCode()); + assertThat(args3.hashCode()).isEqualTo(new ArgsParser("Arg1", "Arg2", Math.PI).args(String.class, String.class, double.class).parse().hashCode()); + assertThat(args4.hashCode()).isEqualTo(new ArgsParser("Arg1", "Arg2", Math.PI, Math.E).args(String.class, String.class, double.class, double.class).parse().hashCode()); + } + + @Test + public void testToString() { + assertThat(args0.toString()).isEmpty(); + assertThat(args1.toString()).isEqualTo("Arg1"); + assertThat(args2.toString()).isEqualTo("Arg1 Arg2"); + assertThat(args3.toString()).isEqualTo("Arg1 Arg2 " + Double.toString(Math.PI)); + assertThat(args4.toString()).isEqualTo("Arg1 Arg2 " + Double.toString(Math.PI) + ' ' + Double.toString(Math.E)); + } +} diff --git a/src/test/java/nova/commands/CommandTest.java b/src/test/java/nova/commands/CommandTest.java new file mode 100644 index 0000000..32acd3b --- /dev/null +++ b/src/test/java/nova/commands/CommandTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ + +package nova.commands; + +import nova.commands.exception.CommandException; +import nova.core.event.bus.Event; +import nova.core.event.bus.EventBus; +import org.junit.Before; +import org.junit.Test; + +import java.util.Optional; + +import static nova.testutils.NovaAssertions.assertThat; + +/** + * Used to test {@link Command commands}. + * + * @author ExE Boss + */ +public class CommandTest { + + public TestCommand command; + + @Before + public void setUp() { + command = new TestCommand("Test_Command"); + } + + @Test + public void testHandle() { + EventBus events = new EventBus<>(); + Args args = new ArgsParser("test").args(String.class).parse(); + events.on(TestCommandHandleEvent.class).bind(evt -> { + assertThat(evt.player).isNull(); + assertThat(evt.command.getCommandName()).isEqualTo(command.getCommandName()); + assertThat(evt.argsObj).isEqualTo(Optional.of(args)); + }); + command.handle(null, true, new String[]{"test"}, Optional.of(events)); + } + + @Test(expected = CommandException.class) + public void testHandleException() { + command.handleError(null, true, new String[0], "Message"); + } + + @Test + public void testGetAutocompleteList() { + assertThat(command.getAutocompleteList(Optional.empty(), true, new String[0], Optional.empty())).isEmpty(); + } + + @Test + public void testGetCommandName() { + assertThat(command.getCommandName()).isEqualTo("Test_Command"); + } + + @Test + public void testGetID() { + assertThat(command.getID()).isEqualTo("test_command"); + } +} diff --git a/src/test/java/nova/commands/TestCommand.java b/src/test/java/nova/commands/TestCommand.java new file mode 100644 index 0000000..142481b --- /dev/null +++ b/src/test/java/nova/commands/TestCommand.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands; + +import nova.commands.exception.CommandException; +import nova.core.entity.component.Player; +import nova.core.event.bus.Event; +import nova.core.event.bus.EventBus; + +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class TestCommand extends Command { + + public TestCommand() { + this("Test_Command"); + } + + public TestCommand(String command) { + super(command); + } + + public void handle(Optional player, boolean prejoined, String[] args, Optional> events) throws CommandException { + Args argsObj = new ArgsParser(args).args(String.class).parse(); + events.ifPresent(evt -> evt.publish(new TestCommandHandleEvent(player, this, argsObj, true, args))); + } + + public void handleError(Optional player, boolean prejoined, String[] args, String message) throws CommandException { + throw new CommandException(message); + } + + @Override + public void handle(Optional player, boolean prejoined, String... args) throws CommandException { + this.handle(player, prejoined, args, Optional.empty()); + } + + @Override + public String getCommandUsage(Optional player) throws CommandException { + return ""; + } +} diff --git a/src/test/java/nova/commands/TestCommandHandleEvent.java b/src/test/java/nova/commands/TestCommandHandleEvent.java new file mode 100644 index 0000000..d0ef9ee --- /dev/null +++ b/src/test/java/nova/commands/TestCommandHandleEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands; + +import nova.commands.event.CommandEvent; +import nova.core.entity.component.Player; + +import java.util.Optional; + +/** + * @author ExE Boss + */ +public class TestCommandHandleEvent extends CommandEvent.CommandHandleEvent { + + public TestCommandHandleEvent(Optional player, Command command, Args argsObj, boolean prejoined, String... args) { + super(player, command, argsObj, prejoined, args); + } + + public TestCommandHandleEvent(Optional player, Command command, boolean prejoined, String... args) { + super(player, command, prejoined, args); + } +} diff --git a/src/test/java/nova/commands/event/CommandEventTest.java b/src/test/java/nova/commands/event/CommandEventTest.java new file mode 100644 index 0000000..84c8668 --- /dev/null +++ b/src/test/java/nova/commands/event/CommandEventTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017 NOVA, All rights reserved. + * This library is free software, licensed under GNU Lesser General Public License version 3 + * + * This file is part of NOVA. + * + * NOVA is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NOVA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NOVA. If not, see . + */ +package nova.commands.event; + +import nova.commands.TestCommand; +import nova.commands.TestCommandHandleEvent; +import nova.core.event.bus.Event; +import nova.core.event.bus.EventBus; +import org.junit.Before; +import org.junit.Test; + +import java.util.Optional; + +import static nova.testutils.NovaAssertions.assertThat; + +/** + * + * @author ExE Boss + */ +public class CommandEventTest { + + public TestCommand command; + public EventBus events; + + @Before + public void setUp() { + events = new EventBus<>(); + command = new TestCommand(); + } + + @Test + public void testCommandRegister() { + events.on(CommandEvent.Register.class).bind(evt -> { + assertThat(evt.command).isEqualTo(command); + }); + events.publish(new CommandEvent.Register(command)); + } +}