Skip to content

Commit 614047a

Browse files
committed
Add support for typed parameter parsing
1 parent d20a99f commit 614047a

File tree

61 files changed

+5574
-344
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+5574
-344
lines changed

config/codenarc/codenarc.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 Björn Kautler
2+
* Copyright 2020 Björn Kautler
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ ruleset {
3333
ComparisonWithSelf {
3434
doNotApplyToClassNames = [
3535
'net.kautler.command.api.AliasAndParameterStringTest',
36+
'net.kautler.command.parameter.ParametersImplTest',
3637
'net.kautler.command.util.lazy.LazyReferenceByFunctionTest',
3738
'net.kautler.command.util.lazy.LazyReferenceBySupplierTest',
3839
'net.kautler.command.util.lazy.LazyReferenceTest'
@@ -304,6 +305,7 @@ ruleset {
304305
ExplicitCallToEqualsMethod {
305306
doNotApplyToClassNames = [
306307
'net.kautler.command.api.AliasAndParameterStringTest',
308+
'net.kautler.command.parameter.ParametersImplTest',
307309
'net.kautler.command.util.lazy.LazyReferenceByFunctionTest',
308310
'net.kautler.command.util.lazy.LazyReferenceBySupplierTest',
309311
'net.kautler.command.util.lazy.LazyReferenceTest'

config/pmd/pmd.xml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0"?>
22
<!--
3-
~ Copyright 2019 Björn Kautler
3+
~ Copyright 2020 Björn Kautler
44
~
55
~ Licensed under the Apache License, Version 2.0 (the "License");
66
~ you may not use this file except in compliance with the License.
@@ -132,9 +132,23 @@
132132
<properties>
133133
<property name="violationSuppressXPath">
134134
<value>
135+
<!-- typeIs('java.lang.Exception') is a work-around for https://github.com/pmd/pmd/issues/2246 -->
135136
.[
136137
typeIsExactly('net.kautler.command.api.Version')
137138
or typeIsExactly('net.kautler.test.pitest.ExplicitMutationFilterDetails')
139+
or typeIs('java.lang.Exception')
140+
]
141+
</value>
142+
</property>
143+
</properties>
144+
</rule>
145+
<rule ref="category/java/design.xml/SignatureDeclareThrowsException">
146+
<properties>
147+
<property name="violationSuppressXPath">
148+
<value>
149+
.[
150+
ancestor::ClassOrInterfaceDeclaration[typeIsExactly('net.kautler.command.api.parameter.ParameterConverter')]
151+
and ancestor::MethodDeclaration/@MethodName = 'convert'
138152
]
139153
</value>
140154
</property>
@@ -145,6 +159,17 @@
145159
<property name="maxmethods" value="15"/>
146160
</properties>
147161
</rule>
162+
<rule ref="category/java/design.xml/UseObjectForClearerAPI">
163+
<properties>
164+
<property name="violationSuppressXPath">
165+
<value>
166+
.[
167+
ancestor::ClassOrInterfaceDeclaration[typeIs('net.kautler.command.api.parameter.ParameterConverter')]
168+
]
169+
</value>
170+
</property>
171+
</properties>
172+
</rule>
148173
<rule ref="category/java/documentation.xml"/>
149174
<rule ref="category/java/documentation.xml/CommentSize">
150175
<properties>
@@ -209,7 +234,10 @@
209234
\QFound 'DU'-anomaly for variable 'seen' (lines '\E.* |
210235
\QFound 'DD'-anomaly for variable 'distance' (lines '\E.* |
211236
\QFound 'DD'-anomaly for variable 'aliasAndParameterString' (lines '\E.* |
212-
\QFound 'DU'-anomaly for variable 'parameterString' (lines '\E.*
237+
\QFound 'DU'-anomaly for variable 'parameterString' (lines '\E.* |
238+
\QFound 'DU'-anomaly for variable 'channelId' (lines '\E.* |
239+
\QFound 'DU'-anomaly for variable 'roleId' (lines '\E.* |
240+
\QFound 'DU'-anomaly for variable 'userId' (lines '\E.*
213241
</value>
214242
</property>
215243
</properties>

config/spotbugs/spotbugs-exclude.xml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
~ Copyright 2019 Björn Kautler
3+
~ Copyright 2020 Björn Kautler
44
~
55
~ Licensed under the Apache License, Version 2.0 (the "License");
66
~ you may not use this file except in compliance with the License.
@@ -62,13 +62,17 @@
6262
<Method name="setAliasAndParameterStringTransformer" params="javax.enterprise.inject.Instance" returns="void"/>
6363
</Or>
6464
</And>
65+
<And>
66+
<Class name="net.kautler.command.parameter.parser.TypedParameterParser"/>
67+
<Method name="setCommandHandlers" params="javax.enterprise.inject.Instance" returns="void"/>
68+
</And>
6569
</Or>
6670
<Bug pattern="UPM_UNCALLED_PRIVATE_METHOD"/>
6771
</Match>
6872

6973
<Match>
70-
<Class name="net.kautler.command.api.ParameterParser"/>
71-
<Method name="getParsedParameters" params="net.kautler.command.api.Command, java.lang.String, java.lang.String, java.lang.String" returns="java.util.Map"/>
74+
<Class name="net.kautler.command.parameter.parser.BaseParameterParser"/>
75+
<Method name="parse" params="net.kautler.command.api.Command, java.lang.String, java.lang.String, java.lang.String, java.util.function.BiFunction" returns="net.kautler.command.api.parameter.Parameters"/>
7276
<Bug pattern="VA_FORMAT_STRING_USES_NEWLINE"/>
7377
</Match>
7478

@@ -137,4 +141,18 @@
137141
<Method name="onEvent" params="net.dv8tion.jda.api.events.GenericEvent" returns="void"/>
138142
<Bug pattern="ITC_INHERITANCE_TYPE_CHECKING"/>
139143
</Match>
144+
145+
<!-- work-around for https://github.com/mebigfatguy/fb-contrib/issues/384 -->
146+
<Match>
147+
<Class name="net.kautler.command.parameter.ParametersImpl"/>
148+
<Method name="get" params="java.lang.String, java.util.function.Supplier" returns="java.lang.Object"/>
149+
<Bug pattern="OI_OPTIONAL_ISSUES_USES_ORELSEGET_WITH_NULL"/>
150+
</Match>
151+
152+
<!-- work-around for https://github.com/mebigfatguy/fb-contrib/issues/385 -->
153+
<Match>
154+
<Class name="net.kautler.command.parameter.converter.javacord.UserMentionConverterJavacord"/>
155+
<Method name="convert" params="java.lang.String, java.lang.String, net.kautler.command.api.Command, org.javacord.api.entity.message.Message, java.lang.String, java.lang.String, java.lang.String" returns="org.javacord.api.entity.user.User"/>
156+
<Bug pattern="AI_ANNOTATION_ISSUES_NEEDS_NULLABLE"/>
157+
</Match>
140158
</FindBugsFilter>

readme/README_template.md

Lines changed: 101 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ Table of Contents
3939
* [Command Restrictions](#command-restrictions)
4040
* [Command Usage](#command-usage)
4141
* [Parsing Parameters](#parsing-parameters)
42+
* [Simple Splitting of Parameters](#simple-splitting-of-parameters)
43+
* [Semantic Parsing and Validation](#semantic-parsing-and-validation)
44+
* [Semantic Parsing and Validation with Type Conversions](#semantic-parsing-and-validation-with-type-conversions)
45+
* [Customizing Parameter Converters](#customizing-parameter-converters)
4246
* [Customizing Command Prefix](#customizing-command-prefix)
4347
* [Customizing Alias Calculation](#customizing-alias-calculation)
4448
* [CDI Events](#cdi-events)
@@ -286,20 +290,24 @@ is described at [Parsing Parameters](#parsing-parameters).
286290

287291
#### Parsing Parameters
288292

289-
There are two helpers to split the `parameterString` that is provided to `Command#execute(...)` into multiple parameters
290-
that can then be handled separately.
293+
There are three helpers to split the `parameterString` that is provided to `Command#execute(...)` into multiple
294+
parameters that can then be handled separately.
295+
296+
##### Simple Splitting of Parameters
291297

292298
The first is the method `Command.getParameters(...)` which you give the parameter string and the maximum amount of
293299
parameters to split into. The provided string will then be split at any arbitrary amount of consecutive whitespace
294300
characters. The last element of the returned array will have all remaining text in the parameter string. If you expect
295301
exactly three parameters without whitespaces, you should set the max parameters to four, so you can easily test the
296302
length of the returned array whether too many parameters were given to the command.
297303

304+
##### Semantic Parsing and Validation
305+
298306
The second is the [`ParameterParser`][ParameterParser JavaDoc] that you can get injected into your command. For the
299307
`ParameterParser` to work, the [usage](#command-usage) of the command has to follow a defined syntax language. This
300308
usage syntax is then parsed and the given parameter string analysed according to the defined syntax. If the given
301-
parameter string does not adhere to the defined syntax, an `IllegalArgumentException` is thrown that can be caught and
302-
reported to the user giving wrong arguments. The exception message is suitable to be directly forwarded to user.
309+
parameter string does not adhere to the defined syntax, a `ParameterParseException` is thrown that can be caught and
310+
reported to the user giving wrong arguments. The exception message is suitable to be directly forwarded to users.
303311

304312
The usage string has to follow this pre-defined format:
305313
* Placeholders for free text without whitespaces (in the value) look like `<my placeholder>`
@@ -311,16 +319,50 @@ The usage string has to follow this pre-defined format:
311319
* Whitespace characters between the defined tokens are optional and ignored
312320

313321
_**Examples:**_
314-
* `@Usage("<coin type> <amount>")}`
315-
* `@Usage("['all'] ['exact']")}`
316-
* `@Usage("[<text...>]")}`
317-
* `@Usage("(<targetLanguage> '|' | <sourceLanguage> <targetLanguage>) <text...>")}`
318-
319-
_**Warning:**_ If you have an optional literal parameter following an optional placeholder parameter like for example
320-
`[<user mention>] ['exact']` and a user invokes the command with only the parameter `exact`, it could fit in both
321-
parameter slots. You have to decide yourself in which slot it belongs. For cases where the literal parameter can never
322-
be meant for the placeholder, you can use `ParameterParser#fixupParsedParameter(...)` to correct the parameters map for
323-
the two given parameters.
322+
* `@Usage("<coin type> <amount>")`
323+
* `@Usage("['all'] ['exact']")`
324+
* `@Usage("[<text...>]")`
325+
* `@Usage("(<targetLanguage> '|' | <sourceLanguage> <targetLanguage>) <text...>")`
326+
327+
The values for these non-typed parameters are always `String`s unless multiple parameters with the same name have a
328+
value given by the user like with the pattern `<foo> <foo>`, in which case the value will be a `List<String>`.
329+
330+
_**Warning:**_ If you for example have
331+
* an optional placeholder followed by an optional literal like in `[<placeholder>] ['literal']` or
332+
* alternatively a placeholder or literal like in `(<placeholder> | 'literal')`
333+
334+
and a user invokes the command with only the parameter `literal`, it could fit in both parameter slots.
335+
You have to decide yourself in which slot it belongs. For cases where the literal parameter can never
336+
be meant for the placeholder, you can use `Parameters#fixup(...)` to correct the parameters instance
337+
for the two given parameters.
338+
339+
##### Semantic Parsing and Validation with Type Conversions
340+
341+
The third is an addendum to the second method described above. The syntax is basically the same. The only difference is,
342+
that a colon (':') followed by a parameter type can optionally be added after a parameter name like for example
343+
`<amount:integer>`. Parameters that do not have a type specified, are implicitly of type `string`. If a colon is needed
344+
within the actual parameter name, a type has to be specified explicitly, as invalid parameter types are not allowed and
345+
will trigger an error at runtime.
346+
347+
The parameter types can be freely defined by supplying [parameter converters](#customizing-parameter-converters) to
348+
define new types or overwrite built-in ones to have a different behavior.
349+
350+
The built-in types currently available are:
351+
* for all message frameworks
352+
* `decimal` for a floating point number converted to `BigDecimal`
353+
* `number` or `integer` for a natural number converted to `BigInteger`
354+
* `string` or `text` for a no-op conversion which can be used if a colon is needed in the parameter name
355+
or if simply all parameters should have a type specified for consistency
356+
* for Javacord
357+
* `user_mention` or `userMention` for a mentioned user converted to `User`
358+
* `role_mention` or `roleMention` for a mentioned role converted to `Role`
359+
* `channel_mention` or `channelMention` for a mentioned channel converted to `Channel`
360+
* for JDA
361+
* `user_mention` or `userMention` for a mentioned user converted to `User`
362+
* `role_mention` or `roleMention` for a mentioned role converted to `Role`
363+
* `channel_mention` or `channelMention` for a mentioned channel converted to `TextChannel`
364+
365+
To select the typed parameter parser, add the qualifier `@ParameterParser.Typed` to the injected `ParameterParser`.
324366

325367
_**Examples:**_
326368
```java
@@ -333,10 +375,10 @@ public class PingCommand implements Command<Message> {
333375
@Override
334376
public void execute(Message incomingMessage, String prefix, String usedAlias, String parameterString) {
335377
try {
336-
parameterParser.getParsedParameters(this, prefix, usedAlias, parameterString);
337-
} catch (IllegalArgumentException e) {
378+
parameterParser.parse(this, incomingMessage, prefix, usedAlias, parameterString);
379+
} catch (ParameterParseException ppe) {
338380
incomingMessage.getChannel()
339-
.sendMessage(format("%s: %s", incomingMessage.getAuthor().getDisplayName(), e.getMessage()))
381+
.sendMessage(format("%s: %s", incomingMessage.getAuthor().getDisplayName(), ppe.getMessage()))
340382
.exceptionally(ExceptionLogger.get());
341383
return;
342384
}
@@ -350,31 +392,57 @@ public class PingCommand implements Command<Message> {
350392

351393
```java
352394
@ApplicationScoped
353-
@Usage("[<user mention>] ['exact']")
395+
@Usage("[<user:userMention>] ['exact']")
354396
public class DoCommand implements Command<Message> {
355397
@Inject
398+
@Typed
356399
private ParameterParser parameterParser;
357400

358401
@Override
359402
public void execute(Message incomingMessage, String prefix, String usedAlias, String parameterString) {
360-
Map<String, String> parameters;
403+
Parameters<String> parameters;
361404
try {
362-
parameters = parameterParser.getParsedParameters(this, prefix, usedAlias, parameterString);
363-
} catch (IllegalArgumentException e) {
405+
parameters = parameterParser.parse(this, incomingMessage, prefix, usedAlias, parameterString);
406+
} catch (ParameterParseException ppe) {
364407
incomingMessage.getChannel()
365-
.sendMessage(format("%s: %s", incomingMessage.getAuthor().getDisplayName(), e.getMessage()))
408+
.sendMessage(format("%s: %s", incomingMessage.getAuthor().getDisplayName(), ppe.getMessage()))
366409
.exceptionally(ExceptionLogger.get());
367410
return;
368411
}
369-
parameterParser.fixupParsedParameter(parameters, "user mention", "exact");
370-
boolean exact = parameters.containsKey("exact");
371-
boolean otherUserGiven = parameters.containsKey("user mention");
372-
String otherUserMention = parameters.get("user mention");
412+
parameters.fixup("user mention", "exact");
413+
boolean exact = parameters.containsParameter("exact");
414+
Optional<String> otherUser = parameters.get("user mention");
373415
// ...
374416
}
375417
}
376418
```
377419

420+
#### Customizing Parameter Converters
421+
422+
A custom parameter converter can be configured by providing a CDI bean that implements the
423+
[`ParameterConverter`][ParameterConverter JavaDoc] interface. In the implementation of the `convert` method the string
424+
parameter and various context information is given and from this the converted parameter value can be calculated.
425+
The class also needs to be annotated with one or multiple [`ParameterType`][ParameterType JavaDoc] qualifiers
426+
that define the parameter type aliases for which the annotated parameter converter works. Without such
427+
qualifier the converter will simply never be used. It is an error to have multiple parameter converters with the
428+
same parameter type that can be applied to the same framework message type and this will produce an error latest
429+
when a parameter with that type is being converted. The only exception are the built-in parameter types.
430+
A user-supplied converter with the same parameter type as a built-in converter will be preferred,
431+
but it would still be an error to have multiple such overrides for the same type.
432+
433+
_**Examples:**_
434+
```java
435+
@ApplicationScoped
436+
@ParameterType("strings")
437+
public class StringsConverter implements ParameterConverter<Object, List<String>> {
438+
@Override
439+
public List<String> convert(String parameter, String type, Command<?> command, Object message,
440+
String prefix, String usedAlias, String parameterString) {
441+
return asList(parameter.split(","));
442+
}
443+
}
444+
```
445+
378446
#### Customizing Command Prefix
379447

380448
A custom command prefix can be configured by providing a CDI bean that implements the
@@ -535,7 +603,7 @@ License
535603
-------
536604

537605
```
538-
Copyright 2019 Björn Kautler
606+
Copyright 2020 Björn Kautler
539607
540608
Licensed under the Apache License, Version 2.0 (the "License");
541609
you may not use this file except in compliance with the License.
@@ -567,7 +635,7 @@ limitations under the License.
567635
[Mutant Coverage Badge]:
568636
https://shields.javacord.org/badge/PIT%20Mutant%20Coverage-100%25-brightgreen.svg?style=flat
569637
[Integration Test Coverage Badge]:
570-
https://shields.javacord.org/badge/Integration%20Test%20Coverage-~75%25-brightgreen.svg?style=flat
638+
https://shields.javacord.org/badge/Integration%20Test%20Coverage-~70%25-brightgreen.svg?style=flat
571639
[Supported Java Versions Badge]:
572640
https://shields.javacord.org/badge/Supported%20Java%20Versions-Java8+-lightgrey.svg
573641
[Supported Message Frameworks Badge]:
@@ -607,8 +675,12 @@ limitations under the License.
607675
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/annotation/Description.html
608676
[NoneOf JavaDoc]:
609677
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/restriction/NoneOf.html
678+
[ParameterConverter JavaDoc]:
679+
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/parameter/ParameterConverter.html
610680
[ParameterParser JavaDoc]:
611-
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/ParameterParser.html
681+
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/parameter/ParameterParser.html
682+
[ParameterType JavaDoc]:
683+
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/parameter/ParameterType.html
612684
[PrefixProvider JavaDoc]:
613685
https://www.javadoc.io/page/net.kautler/command-framework/latest/net/kautler/command/api/prefix/PrefixProvider.html
614686
[AliasAndParameterStringTransformer JavaDoc]:

0 commit comments

Comments
 (0)