Skip to content

Commit ad55836

Browse files
committed
Check actual restriction class instead of inheritance distance
This makes it possible to use non-relaxed created restriction beans and produced restriction beans that do only have Restriction as type of the proxy but not the real class of the contextual instance.
1 parent 6cc418f commit ad55836

File tree

9 files changed

+150
-85
lines changed

9 files changed

+150
-85
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2022 Björn Kautler
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.kautler.command.integ.test.javacord.restriction
18+
19+
import javax.enterprise.context.ApplicationScoped
20+
import javax.enterprise.event.ObservesAsync
21+
import javax.enterprise.inject.Produces
22+
import javax.enterprise.inject.Vetoed
23+
24+
import net.kautler.command.api.CommandContext
25+
import net.kautler.command.api.annotation.RestrictedTo
26+
import net.kautler.command.api.event.javacord.CommandNotAllowedEventJavacord
27+
import net.kautler.command.api.restriction.Restriction
28+
import net.kautler.command.integ.test.javacord.PingIntegTest
29+
import net.kautler.command.integ.test.spock.AddBean
30+
import net.kautler.command.restriction.RestrictionLookup
31+
import org.javacord.api.entity.channel.ServerTextChannel
32+
import org.javacord.api.entity.message.Message
33+
import spock.lang.Specification
34+
import spock.lang.Subject
35+
import spock.util.concurrent.BlockingVariable
36+
37+
@Subject(RestrictionLookup)
38+
class ProducedRestrictionIntegTest extends Specification {
39+
@AddBean(RestrictionProducer)
40+
@AddBean(PingCommand)
41+
def 'produced restrictions should be usable'(ServerTextChannel serverTextChannelAsUser) {
42+
given:
43+
def commandNotAllowedEventReceived = new BlockingVariable<Boolean>(System.properties.testResponseTimeout as double)
44+
PingCommand.commandNotAllowedEventReceived = commandNotAllowedEventReceived
45+
46+
when:
47+
serverTextChannelAsUser
48+
.sendMessage('!ping')
49+
.join()
50+
51+
then:
52+
commandNotAllowedEventReceived.get()
53+
}
54+
55+
@RestrictedTo(Restriction1)
56+
static class PingCommand extends PingIntegTest.PingCommand {
57+
static commandNotAllowedEventReceived
58+
59+
void handleCommandNotAllowedEvent(@ObservesAsync CommandNotAllowedEventJavacord commandNotAllowedEvent) {
60+
commandNotAllowedEventReceived?.set(commandNotAllowedEvent)
61+
}
62+
}
63+
64+
static class Restriction1 implements Restriction<Message> {
65+
@Override
66+
boolean allowCommand(CommandContext<? extends Message> commandContext) {
67+
false
68+
}
69+
}
70+
71+
@Vetoed
72+
@ApplicationScoped
73+
static class RestrictionProducer {
74+
@Produces
75+
@ApplicationScoped
76+
Restriction<Message> restriction = new Restriction1()
77+
}
78+
}

src/main/java/net/kautler/command/api/restriction/Restriction.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424
* @param <M> the class of the messages for which this restriction can check allowance
2525
*/
2626
public interface Restriction<M> {
27+
/**
28+
* Returns the real class of this restriction. Subclasses usually do not need to overwrite this method as the
29+
* default implementation should be appropriate. This is necessary as CDI implementations that create
30+
* proxies might not provide the real class that is necessary for restriction lookup.
31+
*
32+
* @return the real class of this restriction
33+
*/
34+
default Class<?> getRealClass() {
35+
return this.getClass();
36+
}
37+
2738
/**
2839
* Returns whether a command caused by the given command context should be allowed by this restriction or not.
2940
*

src/main/java/net/kautler/command/restriction/RestrictionLookup.java

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package net.kautler.command.restriction;
1818

19-
import net.kautler.command.api.restriction.Restriction;
20-
2119
import java.util.Collection;
2220
import java.util.Map;
2321
import java.util.Objects;
@@ -26,7 +24,7 @@
2624
import java.util.concurrent.ConcurrentHashMap;
2725
import java.util.concurrent.CopyOnWriteArraySet;
2826

29-
import static java.util.Comparator.comparingInt;
27+
import net.kautler.command.api.restriction.Restriction;
3028

3129
/**
3230
* A directory of restrictions that can be looked up by their type.
@@ -40,8 +38,7 @@ public class RestrictionLookup<M> {
4038
private final Set<Restriction<? super M>> restrictions = new CopyOnWriteArraySet<>();
4139

4240
/**
43-
* The restrictions by class. As the actual restriction instances are proxied by CDI, this map cannot be
44-
* built automatically from the available restrictions, but only on the fly.
41+
* The restrictions by real class.
4542
*/
4643
private final Map<Class<?>, Restriction<? super M>> restrictionByClass = new ConcurrentHashMap<>();
4744

@@ -52,7 +49,7 @@ public class RestrictionLookup<M> {
5249
*/
5350
public void addAllRestrictions(Collection<Restriction<? super M>> restrictions) {
5451
this.restrictions.addAll(restrictions);
55-
restrictionByClass.clear();
52+
restrictions.forEach(restriction -> restrictionByClass.put(restriction.getRealClass(), restriction));
5653
}
5754

5855
/**
@@ -62,30 +59,7 @@ public void addAllRestrictions(Collection<Restriction<? super M>> restrictions)
6259
* @return the restriction instance that fits to the given class or {@code null}
6360
*/
6461
public Restriction<? super M> getRestriction(Class<?> restrictionClass) {
65-
return restrictionByClass
66-
.computeIfAbsent(restrictionClass,
67-
key -> restrictions.stream()
68-
// we cannot use a map as the classes are proxied by CDI
69-
.filter(key::isInstance)
70-
.min(comparingInt(restriction -> getInheritanceDistance(restriction, key)))
71-
.orElse(null));
72-
}
73-
74-
/**
75-
* Calculates the distance between the class of the given restriction and one of its ancestors or itself.
76-
*
77-
* @param restriction the restriction to check
78-
* @param restrictionClass the ancestor or self to calculate distance from
79-
* @return the distance between the class of the given restriction and the given class
80-
*/
81-
private static int getInheritanceDistance(Restriction<?> restriction, Class<?> restrictionClass) {
82-
int distance = 0;
83-
for (Class<?> clazz = restriction.getClass();
84-
!clazz.equals(restrictionClass) && restrictionClass.isAssignableFrom(clazz);
85-
clazz = clazz.getSuperclass()) {
86-
distance++;
87-
}
88-
return distance;
62+
return restrictionByClass.get(restrictionClass);
8963
}
9064

9165
@Override

src/pitest/java/net/kautler/test/pitest/ExplicitMutationFilter.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,6 @@ public class ExplicitMutationFilter implements MutationInterceptor {
133133
"()V",
134134
"org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator",
135135
"Substituted 3 with 4"),
136-
// this causes an endless for-loop
137-
new ExplicitMutationFilterDetails(
138-
"net.kautler.command.restriction.RestrictionLookup",
139-
"getInheritanceDistance",
140-
"(Lnet/kautler/command/api/restriction/Restriction;Ljava/lang/Class;)I",
141-
"org.pitest.mutationtest.engine.gregor.mutators.experimental.NakedReceiverMutator",
142-
"replaced call to java/lang/Class::getSuperclass with receiver"),
143136
// giving a 4 instead of 3 element array to String.format cannot be killed
144137
new ExplicitMutationFilterDetails(
145138
"net.kautler.command.usage.UsageErrorListener",

src/test/groovy/net/kautler/command/api/CommandHandlerTest.groovy

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,15 @@ class CommandHandlerTest extends Specification {
6666

6767
CommandHandler<Object> commandHandlerDelegate = Mock()
6868

69-
Restriction<Object> restriction1 = Stub()
69+
Restriction<Object> restriction1 = Stub {
70+
it.realClass >> { callRealMethod() }
71+
}
7072

71-
Restriction<Object> restriction2 = Stub()
73+
// additional interface is just that the two restrictions have different
74+
// classes, so that the restriction lookup can handle them properly
75+
Restriction<Object> restriction2 = Stub(additionalInterfaces: [Serializable]) {
76+
it.realClass >> { callRealMethod() }
77+
}
7278

7379
Command<Object> command1 = Mock()
7480

@@ -364,7 +370,7 @@ class CommandHandlerTest extends Specification {
364370
commandsInstance.eachWithIndex { command, i ->
365371
with(command.ci()) {
366372
it.aliases >> ["test$i" as String]
367-
it.restrictionChain >> new RestrictionChainElement(Restriction)
373+
it.restrictionChain >> new RestrictionChainElement(restriction1.getClass())
368374
}
369375
}
370376
commandHandler.doSetCommands(commandsInstance)

src/test/groovy/net/kautler/command/api/restriction/RestrictionChainElementTest.groovy

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ import spock.lang.Subject
2424
import static org.powermock.reflect.Whitebox.getAllInstanceFields
2525

2626
class RestrictionChainElementTest extends Specification {
27-
Restriction<Object> restriction1 = Stub()
27+
Restriction<Object> restriction1 = Stub {
28+
it.realClass >> { callRealMethod() }
29+
}
2830

2931
// additional interface is just that the two restrictions have different
3032
// classes, so that the restriction lookup can handle them properly
31-
Restriction<Object> restriction2 = Stub(additionalInterfaces: [Serializable])
33+
Restriction<Object> restriction2 = Stub(additionalInterfaces: [Serializable]) {
34+
it.realClass >> { callRealMethod() }
35+
}
3236

3337
@Subject
3438
RestrictionChainElement restrictionChainElement = new RestrictionChainElement(restriction1.getClass())
@@ -348,8 +352,8 @@ class RestrictionChainElementTest extends Specification {
348352
then:
349353
toStringResult.contains("$field.name=")
350354
field.type == String ?
351-
toStringResult.contains("'${field.get(testee)}'") :
352-
toStringResult.contains(String.valueOf(field.get(testee)))
355+
toStringResult.contains("'${field.get(testee)}'") :
356+
toStringResult.contains(String.valueOf(field.get(testee)))
353357

354358
where:
355359
[testee, field] << [

src/test/groovy/net/kautler/command/handler/CommandHandlerJavacordTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,13 @@ class CommandHandlerJavacordTest extends Specification {
7777
}
7878

7979
Restriction<Object> restriction = Stub {
80+
it.realClass >> { callRealMethod() }
8081
allowCommand(_) >> false
8182
}
8283

8384
Command<Object> command = Stub {
8485
it.aliases >> ['test']
85-
it.restrictionChain >> new RestrictionChainElement(Restriction)
86+
it.restrictionChain >> new RestrictionChainElement(restriction.getClass())
8687
}
8788

8889
TestEventReceiver testEventReceiverDelegate = Mock()

src/test/groovy/net/kautler/command/handler/CommandHandlerJdaTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ class CommandHandlerJdaTest extends Specification {
6666
ShardManager shardManagerInCollection2 = Mock()
6767

6868
Restriction<Object> restriction = Stub {
69+
it.realClass >> { callRealMethod() }
6970
allowCommand(_) >> false
7071
}
7172

7273
Command<Object> command = Stub {
7374
it.aliases >> ['test']
74-
it.restrictionChain >> new RestrictionChainElement(Restriction)
75+
it.restrictionChain >> new RestrictionChainElement(restriction.getClass())
7576
}
7677

7778
TestEventReceiver testEventReceiverDelegate = Mock()

0 commit comments

Comments
 (0)