Completed
Push — master ( 2daf05...563beb )
by Roannel Fernández
02:41
created

addCommand(Command)   C

Complexity

Conditions 9

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 19
c 1
b 0
f 0
dl 0
loc 29
rs 6.6666
1
package com.github.netkorp.telegram.framework.managers;
0 ignored issues
show
Code Smell introduced by
It is a best practice to supply a copyright/licence header in your code. Your organisation probably has a template for that.
Loading history...
2
3
import com.github.netkorp.telegram.framework.annotations.TelegramCommand;
4
import com.github.netkorp.telegram.framework.commands.interfaces.Command;
5
import com.github.netkorp.telegram.framework.commands.interfaces.HelpCommand;
6
import com.github.netkorp.telegram.framework.commands.interfaces.MultistageCommand;
7
import com.github.netkorp.telegram.framework.commands.multistage.MultistageCloseCommand;
8
import com.github.netkorp.telegram.framework.commands.multistage.MultistageDoneCommand;
9
import com.github.netkorp.telegram.framework.properties.CommandProperties;
10
import com.github.netkorp.telegram.framework.exceptions.CommandNotActive;
11
import com.github.netkorp.telegram.framework.exceptions.CommandNotFound;
12
import org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
14
import org.springframework.beans.factory.annotation.Autowired;
15
import org.springframework.stereotype.Component;
16
17
import java.lang.invoke.MethodHandles;
18
import java.util.HashMap;
19
import java.util.LinkedList;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.Optional;
23
24
/**
25
 * Provides the component for managing all of the commands available in the bot.
26
 * It includes the management of non-secure commands, active command, basic commands and
27
 * those commands that are involved in the multistage command flow.
28
 */
29
@SuppressWarnings("WeakerAccess")
0 ignored issues
show
Best Practice introduced by
Suppressing code checker warnings is not a best practice. Consider working on your code until it meets guidelines.
Loading history...
30
@Component
31
public class CommandManager {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
32
33
    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 3.
Loading history...
34
35
    /**
36
     * The list of the available commands into a map to get a quick access from its name.
37
     */
38
    private final Map<String, Command> commandsByFullName;
39
40
    /**
41
     * The list of the available commands.
42
     */
43
    private final List<Command> commands;
44
45
    /**
46
     * The list of the non-secure commands.
47
     */
48
    private final List<Command> nonSecureCommands;
49
50
    /**
51
     * The command that is active for each user.
52
     */
53
    private final Map<Long, MultistageCommand> activeCommand;
54
55
    /**
56
     * The properties of the commands.
57
     */
58
    private final CommandProperties commandProperties;
59
60
    /**
61
     * The command that closes an active conversation with the bot, indicating to the active command that the conversation is closed.
0 ignored issues
show
Coding Style introduced by
This line is 133 characters long, which is over the set limit of 120 characters.
Loading history...
62
     * It is kept here for a quick access to the close command.
63
     */
64
    private MultistageCloseCommand closeCommand;
65
66
    /**
67
     * The command that closes an active conversation with the bot, indicating to the active command that the conversation is done.
0 ignored issues
show
Coding Style introduced by
This line is 131 characters long, which is over the set limit of 120 characters.
Loading history...
68
     * It is kept here for a quick access to the done command.
69
     */
70
    private MultistageDoneCommand doneCommand;
71
72
    /**
73
     * The command that shows the help of the bot.
74
     * It is kept here for a quick access to the help command.
75
     */
76
    private HelpCommand helpCommand;
77
78
    /**
79
     * Constructs a new {@link CommandManager} instance with the list of available {@link Command}
80
     * and the properties of the commands.
81
     *
82
     * @param commands          the list of available {@link Command}.
83
     * @param commandProperties the properties of the commands.
84
     */
85
    @Autowired
86
    public CommandManager(List<Command> commands, CommandProperties commandProperties) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
87
        this.commandsByFullName = new HashMap<>();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
88
        this.commands = new LinkedList<>();
89
        this.nonSecureCommands = new LinkedList<>();
90
91
        this.activeCommand = new HashMap<>();
92
        this.commandProperties = commandProperties;
93
94
        commands.stream()
95
                .filter(item -> item.getClass().isAnnotationPresent(TelegramCommand.class))
0 ignored issues
show
introduced by
Specify a type for: 'item'
Loading history...
96
                .forEach(this::addCommand);
97
    }
98
99
    /**
100
     * Adds the command to the list of available/non-secure commands and sets the commands for
101
     * {@link #closeCommand}, {@link #doneCommand} and {@link #helpCommand}.
102
     *
103
     * @param command the command to be added.
104
     * @see #commandsByFullName
105
     * @see #commands
106
     * @see #nonSecureCommands
107
     * @see #closeCommand
108
     * @see #doneCommand
109
     * @see #helpCommand
110
     */
111
    private void addCommand(Command command) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
112
        // Registering the command for each name
113
        for (String name : getCommandNames(command)) {
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
114
115
            if (!isValidCommandName(name)) {
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
116
                LOG.warn(String.format("Command %s has a duplicate or empty name. It will be discarded.",
0 ignored issues
show
introduced by
Invoke method(s) only conditionally.
Loading history...
Coding Style introduced by
Make this line start at column 9.
Loading history...
117
                        command.getClass().getSimpleName()));
118
                break;
119
            }
120
121
            this.commandsByFullName.put(getCommandFullName(name), command);
122
123
            if (!this.commands.contains(command)) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
124
                this.commands.add(command);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
125
            }
126
127
            // Just for keeping a reference of the non-secure commands
128
            if ((commandProperties.getNonSecure().contains(name)
129
                    || !command.getClass().getAnnotation(TelegramCommand.class).secure())
130
                    && !this.nonSecureCommands.contains(command)) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
131
                this.nonSecureCommands.add(command);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
132
            }
133
134
            if (command instanceof MultistageCloseCommand) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
135
                closeCommand = (MultistageCloseCommand) command;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
136
            } else if (command instanceof MultistageDoneCommand) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
Coding Style introduced by
Move this "else" keyword to a new dedicated line.
Loading history...
137
                doneCommand = (MultistageDoneCommand) command;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
138
            } else if (command instanceof HelpCommand) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
Coding Style introduced by
Move this "else" keyword to a new dedicated line.
Loading history...
139
                helpCommand = (HelpCommand) command;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
140
            }
141
        }
142
    }
143
144
    /**
145
     * Returns {@code true} if the command name is a valid name for a command.
146
     *
147
     * @param commandName the name of the command to validate.
148
     * @return {@code true} if the command name is a valid name for a command; {@code false} otherwise.
149
     */
150
    private boolean isValidCommandName(String commandName) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
151
        return !commandName.trim().isEmpty() && !this.commandsByFullName.containsKey(commandName);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
152
    }
153
154
    /**
155
     * Returns {@code true} if the command is non-secure.
156
     *
157
     * @param command the command to be processed.
158
     * @return {@code true} if the command is non-secure; {@code false} otherwise.
159
     */
160
    public boolean isNonSecureCommand(Command command) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
161
        return this.nonSecureCommands.contains(command);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
162
    }
163
164
    /**
165
     * Returns the names of the command (the same names declared on {@link TelegramCommand#name()}).
166
     * The names may include the slash (/).
167
     *
168
     * @param command the command from which the names will be identified.
169
     * @return the names of the command.
170
     */
171
    public static String[] getCommandNames(Command command) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
172
        return command.getClass().getAnnotation(TelegramCommand.class).name();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
173
    }
174
175
    /**
176
     * Returns the name of the command. It includes the slash (/).
177
     *
178
     * @param commandName the command's name (the same name declared on {@link TelegramCommand#name()})
179
     * @return the name of the command.
180
     */
181
    public static String getCommandFullName(String commandName) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
182
        return commandName.startsWith("/") ? commandName : String.format("/%s", commandName);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
183
    }
184
185
    /**
186
     * Returns the names of the command. They include the slash (/).
187
     *
188
     * @param command the command from which the names will be identified.
189
     * @return the names of the command.
190
     */
191
    public static List<String> getCommandFullNames(Command command) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
192
        List<String> commandFullNames = new LinkedList<>();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
193
194
        for (String name : getCommandNames(command)) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
195
            commandFullNames.add(getCommandFullName(name));
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
196
        }
197
198
        return commandFullNames;
199
    }
200
201
    /**
202
     * Returns the {@link Command} instance from the command name.
203
     *
204
     * @param command the command name.
205
     * @return the {@link Command} instance.
206
     * @throws CommandNotFound if the name is not related to any commands.
207
     */
208
    public Command getCommand(String command) throws CommandNotFound {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
209
        if (!this.commandsByFullName.containsKey(command)) {
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
210
            throw new CommandNotFound();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
Best Practice introduced by
As defined in your coding style, remove the checked exception: CommandNotFound. Consider throwing an unchecked exception instead.

Opionions on checked exceptions are divided.

On the positive side they do make code safer and provide self-documentation on possible error conditions that the caller has to be aware of.

On the negative side, they do force the caller to deal with these errors, either by catching or specifying them in the method definition. This may make the code more verbose than necessary.

The Java documentation recommends using them wisely.

Loading history...
211
        }
212
213
        return this.commandsByFullName.get(command);
214
    }
215
216
    /**
217
     * Sets the multistage command as the active one.
218
     *
219
     * @param idChat  the chat identification of the user for whom the multistage command will be active.
220
     * @param command the command to activate.
221
     */
222
    public void setActiveCommand(final Long idChat, final MultistageCommand command) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
223
        this.activeCommand.put(idChat, command);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
224
    }
225
226
    /**
227
     * Gets the active command for the user.
228
     *
229
     * @param idChat the chat identification of the user.
230
     * @return the active command.
231
     * @throws CommandNotActive if there is no an active command.
232
     */
233
    public MultistageCommand getActiveCommand(Long idChat) throws CommandNotActive {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
234
        if (!hasActiveCommand(idChat)) {
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
235
            throw new CommandNotActive();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
Best Practice introduced by
As defined in your coding style, remove the checked exception: CommandNotActive. Consider throwing an unchecked exception instead.

Opionions on checked exceptions are divided.

On the positive side they do make code safer and provide self-documentation on possible error conditions that the caller has to be aware of.

On the negative side, they do force the caller to deal with these errors, either by catching or specifying them in the method definition. This may make the code more verbose than necessary.

The Java documentation recommends using them wisely.

Loading history...
236
        }
237
238
        return activeCommand.get(idChat);
239
    }
240
241
    /**
242
     * Removes the active command.
243
     *
244
     * @param idChat the chat identification of the user.
245
     */
246
    public void removeActiveCommand(Long idChat) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
247
        activeCommand.remove(idChat);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
248
    }
249
250
    /**
251
     * Returns {@code true} if there is an active command.
252
     *
253
     * @param idChat the chat identification of the user.
254
     * @return {@code true} if there is an active command; {@code false} otherwise.
255
     */
256
    public boolean hasActiveCommand(Long idChat) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
257
        return activeCommand.containsKey(idChat);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
258
    }
259
260
    /**
261
     * Returns the {@link MultistageCloseCommand} if it exists.
262
     *
263
     * @return the {@link MultistageCloseCommand} instance.
264
     */
265
    public Optional<MultistageCloseCommand> getCloseCommand() {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
266
        return Optional.ofNullable(this.closeCommand);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
267
    }
268
269
    /**
270
     * Returns the {@link MultistageDoneCommand} if it exists.
271
     *
272
     * @return the {@link MultistageDoneCommand} instance.
273
     */
274
    public Optional<MultistageDoneCommand> getDoneCommand() {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
275
        return Optional.ofNullable(this.doneCommand);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
276
    }
277
278
    /**
279
     * Returns the {@link HelpCommand} if it exists.
280
     *
281
     * @return the {@link HelpCommand} instance.
282
     */
283
    public Optional<HelpCommand> getHelpCommand() {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
284
        return Optional.ofNullable(this.helpCommand);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
285
    }
286
287
    /**
288
     * Returns a list with the available commands.
289
     *
290
     * @return the available commands.
291
     */
292
    public List<Command> getAvailableCommands() {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
293
        return commands;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
Security Comprehensibility introduced by
Mutable class members should never be returned to a caller or accepted and stored directly. You might want to Return a copy of commands instead.
Loading history...
294
    }
295
296
    /**
297
     * Returns a list with the available non-secure commands.
298
     *
299
     * @return the available non-secure commands.
300
     */
301
    public List<Command> getAvailableNonSecureCommands() {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
302
        return nonSecureCommands;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
Security Comprehensibility introduced by
Mutable class members should never be returned to a caller or accepted and stored directly. You might want to Return a copy of nonSecureCommands instead.
Loading history...
303
    }
304
}
305