Completed
Push — master ( b73ca5...090342 )
by Roannel Fernández
03:30
created

getAvailableCommands(Long)   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
1
package com.github.netkorp.telegram.framework.commands.basic;
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.abstracts.AbstractSimpleCommand;
5
import com.github.netkorp.telegram.framework.commands.interfaces.Command;
6
import com.github.netkorp.telegram.framework.commands.interfaces.HelpCommand;
7
import com.github.netkorp.telegram.framework.condition.ExcludeCondition;
8
import com.github.netkorp.telegram.framework.exceptions.CommandNotFound;
9
import com.github.netkorp.telegram.framework.managers.CommandManager;
10
import com.github.netkorp.telegram.framework.managers.SecurityManager;
11
import org.apache.logging.log4j.util.Strings;
12
import org.springframework.beans.factory.annotation.Autowired;
13
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
14
import org.springframework.context.NoSuchMessageException;
15
import org.springframework.context.annotation.Conditional;
16
import org.springframework.context.i18n.LocaleContextHolder;
17
import org.telegram.telegrambots.meta.api.objects.Update;
18
19
import java.util.Collection;
20
import java.util.LinkedList;
21
import java.util.List;
22
import java.util.Objects;
23
import java.util.SortedMap;
24
import java.util.StringJoiner;
25
import java.util.TreeMap;
26
27
/**
28
 * Displays the bot's help.
29
 */
30
@TelegramCommand(name = "help", group = "commands.groups.help", description = "commands.description.help")
31
@Conditional(ExcludeCondition.class)
32
@ConditionalOnSingleCandidate(HelpCommand.class)
33
public class BasicHelpCommand extends AbstractSimpleCommand implements HelpCommand {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
34
35
    /**
36
     * The component to know which user is authorized.
37
     */
38
    private final SecurityManager securityManager;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 3.
Loading history...
39
40
    /**
41
     * Constructs a new {@link BasicHelpCommand} instance with the {@link SecurityManager} component instance.
42
     *
43
     * @param securityManager the {@link SecurityManager} component instance.
44
     */
45
    @Autowired
46
    public BasicHelpCommand(SecurityManager securityManager) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
47
        this.securityManager = securityManager;
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
48
    }
49
50
    /**
51
     * Processes the data sent by the users.
52
     *
53
     * @param update the received message.
54
     * @param args   the parameters passed to the command execution.
55
     */
56
    @Override
57
    public void execute(final Update update, String[] args) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
58
        try {
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...
59
            if (args.length == 0) {
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...
60
                execute(update);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
61
                return;
62
            }
63
64
            StringJoiner stringJoiner = new StringJoiner(System.lineSeparator());
65
66
            for (String arg : args) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
67
                try {
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
Comprehensibility introduced by
Extract this nested try block into a separate method.
Loading history...
68
                    stringJoiner.add(helpForCommand(commandManager.getCommand(CommandManager.getCommandFullName(arg))));
0 ignored issues
show
Coding Style introduced by
Make this line start at column 11.
Loading history...
69
                } catch (CommandNotFound commandNotFound) {
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 "catch" keyword to a new dedicated line.
Loading history...
70
                    bot.sendMessage(String.format("%s: %s", commandNotFound.getMessage(), arg), update.getMessage().getChatId(), true);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 11.
Loading history...
Coding Style introduced by
This line is 135 characters long, which is over the set limit of 120 characters.
Loading history...
71
                    execute(update);
72
                    throw commandNotFound;
0 ignored issues
show
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...
73
                }
74
            }
75
76
            bot.sendMessage(stringJoiner.toString(), update.getMessage().getChatId(), true);
77
        } catch (CommandNotFound commandNotFound) {
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 "catch" keyword to a new dedicated line.
Loading history...
Best Practice introduced by
Ignoring an Exception may lead to hard to find bugs. Consider logging or rethrowing the original exception. If you want to throw a different exception, you can set the original exception as its cause to preserve the stacktrace.

When instantiating a new Exception, you can set another Exception as its cause.

See the Oracle documentation on Throwables.

Usage example

throw new Exception("Exception Message", originalException);

Complete Example:

class ReThrowException {
  public static void throwsException() {
        try {
            throw new Exception("I am the original exception");
        } catch (final Exception e) {
            throw new RuntimeException("I am the new exception", e);
        }
    }
    public static void main(String[] args) {
        try {
            throwsException();
        }
        catch (final RuntimeException e) {
            System.out.println(e.getMessage());
            System.out.println("and my cause is: " + e.getCause().getMessage());
            e.printStackTrace();
        }
    }
}
Loading history...
78
            // Do nothing
79
        }
80
    }
81
82
    /**
83
     * Executes the command's logic without taking parameters.
84
     *
85
     * @param update the received message.
86
     */
87
    @Override
88
    public void execute(Update update) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
89
        StringJoiner stringJoiner = new StringJoiner(System.lineSeparator());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
90
91
        stringJoiner.add(String.format("%s:", messageSource.getMessage("commands.basic.help.title", null,
92
                LocaleContextHolder.getLocale())));
93
94
        commandsByGroup(getAvailableCommands(update.getMessage().getChatId()))
95
                .forEach((group, commands) -> stringJoiner.add(helpForGroup(group, commands)));
0 ignored issues
show
introduced by
Specify a type for: 'group', 'commands'
Loading history...
96
97
        bot.sendMessage(stringJoiner.toString(), update.getMessage().getChatId(), true);
98
    }
99
100
    /**
101
     * Returns the help for the group of commands.
102
     *
103
     * @param group    the name of the group.
104
     * @param commands the commands into the group.
105
     * @return the help for the group.
106
     */
107
    private String helpForGroup(String group, List<Command> commands) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
108
        StringJoiner stringJoiner = new StringJoiner(System.lineSeparator());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
109
        if (!Strings.isEmpty(group)) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
110
            stringJoiner.add(String.format("<b>%s</b>", group));
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
111
        }
112
        commands.forEach(command -> stringJoiner.add(helpForCommand(command)));
0 ignored issues
show
introduced by
Specify a type for: 'command'
Loading history...
113
        return System.lineSeparator() + stringJoiner.toString();
114
    }
115
116
    /**
117
     * Returns the help for a single command.
118
     *
119
     * @param command the command.
120
     * @return the help of the command.
121
     */
122
    private String helpForCommand(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...
123
        StringJoiner stringJoiner = new StringJoiner(", ");
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
124
        CommandManager.getCommandFullNames(command).forEach(stringJoiner::add);
125
        return String.format("%s - %s", stringJoiner.toString(), getDescription(command));
126
    }
127
128
    /**
129
     * Organizes the available commands into groups.
130
     *
131
     * @param commands the list with all the available commands.
132
     * @return the available commands in groups sorted by the group's name.
133
     */
134
    private SortedMap<String, List<Command>> commandsByGroup(Collection<Command> commands) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
135
        SortedMap<String, List<Command>> commandsByGroup = new TreeMap<>();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
136
137
        commands.forEach(command -> {
0 ignored issues
show
introduced by
Specify a type for: 'command'
Loading history...
138
            String group = getGroupName(command);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 11.
Loading history...
139
140
            List<Command> commandList = commandsByGroup.getOrDefault(group, new LinkedList<>());
141
            commandList.add(command);
142
143
            commandsByGroup.put(group, commandList);
144
        });
145
146
        return commandsByGroup;
147
    }
148
149
    /**
150
     * Cleans the class name by removing the word "command" from it.
151
     *
152
     * @param commandClass the class to be cleaned.
153
     * @return the cleaned class name.
154
     */
155
    private String cleanCommandClassName(Class<?> commandClass) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
Comprehensibility introduced by
Private methods that do not access instance data should be made static. This makes the code more readable and may enable the compiler to optimize your code. Consider making cleanCommandClassNamestatic.
Loading history...
156
        String className = commandClass.getSimpleName().toLowerCase();
0 ignored issues
show
introduced by
Define the locale to be used in this String operation.
Loading history...
Coding Style introduced by
Make this line start at column 5.
Loading history...
157
        if (!"command".equals(className) && className.endsWith("command")) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
158
            className = className.substring(0, className.length() - 7);
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
Comprehensibility introduced by
Consider assigning this magic number 7 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
159
        }
160
161
        return className;
162
    }
163
164
    /**
165
     * Returns the group's name of the command.
166
     *
167
     * @param command the command from which the group's name will be retrieved.
168
     * @return the group's name.
169
     */
170
    private String getGroupName(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...
171
        String groupName = command.getClass().getAnnotation(TelegramCommand.class).group().trim();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
172
173
        // If there is no an explicit group, we'll try to generate a key to retrieve a message
174
        String key = "";
175
        if (groupName.isEmpty()) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
176
            key = "commands.groups." + cleanCommandClassName(command.getClass());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
177
            groupName = key;
178
        }
179
180
        try {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
181
            groupName = messageSource.getMessage(groupName, null, LocaleContextHolder.getLocale());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
182
        } catch (NoSuchMessageException exception) {
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 "catch" keyword to a new dedicated line.
Loading history...
Best Practice introduced by
Ignoring an Exception may lead to hard to find bugs. Consider logging or rethrowing the original exception. If you want to throw a different exception, you can set the original exception as its cause to preserve the stacktrace.

When instantiating a new Exception, you can set another Exception as its cause.

See the Oracle documentation on Throwables.

Usage example

throw new Exception("Exception Message", originalException);

Complete Example:

class ReThrowException {
  public static void throwsException() {
        try {
            throw new Exception("I am the original exception");
        } catch (final Exception e) {
            throw new RuntimeException("I am the new exception", e);
        }
    }
    public static void main(String[] args) {
        try {
            throwsException();
        }
        catch (final RuntimeException e) {
            System.out.println(e.getMessage());
            System.out.println("and my cause is: " + e.getCause().getMessage());
            e.printStackTrace();
        }
    }
}
Loading history...
183
            if (Objects.equals(key, groupName)) {
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...
184
                groupName = "";
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
185
            }
186
        }
187
188
        return groupName;
189
    }
190
191
    /**
192
     * Returns the description of the command.
193
     *
194
     * @param command the command from which the description will be retrieved.
195
     * @return the command's description.
196
     */
197
    private String getDescription(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...
198
        String description = command.getClass().getAnnotation(TelegramCommand.class).description().trim();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 5.
Loading history...
199
200
        // If there is no explicit description, we'll try to generate a description key to retrieve a message
201
        String key = "";
202
        if (description.isEmpty()) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
203
            key = "commands.description." + cleanCommandClassName(command.getClass());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
204
            description = key;
205
        }
206
207
        try {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
208
            description = messageSource.getMessage(description, null, LocaleContextHolder.getLocale());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
209
        } catch (NoSuchMessageException exception) {
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 "catch" keyword to a new dedicated line.
Loading history...
Best Practice introduced by
Ignoring an Exception may lead to hard to find bugs. Consider logging or rethrowing the original exception. If you want to throw a different exception, you can set the original exception as its cause to preserve the stacktrace.

When instantiating a new Exception, you can set another Exception as its cause.

See the Oracle documentation on Throwables.

Usage example

throw new Exception("Exception Message", originalException);

Complete Example:

class ReThrowException {
  public static void throwsException() {
        try {
            throw new Exception("I am the original exception");
        } catch (final Exception e) {
            throw new RuntimeException("I am the new exception", e);
        }
    }
    public static void main(String[] args) {
        try {
            throwsException();
        }
        catch (final RuntimeException e) {
            System.out.println(e.getMessage());
            System.out.println("and my cause is: " + e.getCause().getMessage());
            e.printStackTrace();
        }
    }
}
Loading history...
210
            if (Objects.equals(key, description)) {
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...
211
                description = messageSource.getMessage("commands.basic.help.default-description", null, LocaleContextHolder.getLocale());
0 ignored issues
show
Coding Style introduced by
Make this line start at column 9.
Loading history...
Coding Style introduced by
This line is 137 characters long, which is over the set limit of 120 characters.
Loading history...
212
            }
213
        }
214
215
        return description;
216
    }
217
218
    /**
219
     * Returns the list of available commands for the user. It takes into account whether the user is authorized or not.
220
     *
221
     * @param chatId the identification of the user's chat.
222
     * @return the list of available commands.
223
     */
224
    private Collection<Command> getAvailableCommands(Long chatId) {
0 ignored issues
show
Coding Style introduced by
Move this left curly brace to the beginning of next line of code.
Loading history...
225
        if (securityManager.isAuthorized(chatId)) {
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...
226
            return commandManager.getAvailableCommands();
0 ignored issues
show
Coding Style introduced by
Make this line start at column 7.
Loading history...
227
        }
228
229
        return commandManager.getAvailableNonSecureCommands();
230
    }
231
}
232