Skip to content

Commit

Permalink
GH-444 Improve parsers caches (#451)
Browse files Browse the repository at this point in the history
* Parser caching.

* Improve performance of Argument#hashcode

* Remove ConcurrentHashMap from BiHashMap. Add unit tests for async arguments initialize.

* Fix BlockingArgumentResolver

* Fix javadocs.
  • Loading branch information
Rollczi authored Oct 18, 2024
1 parent eea80a2 commit 4ea68bc
Show file tree
Hide file tree
Showing 24 changed files with 256 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@

import dev.rollczi.litecommands.annotations.AnnotationInvoker;
import dev.rollczi.litecommands.annotations.AnnotationProcessor;
import dev.rollczi.litecommands.meta.Meta;
import dev.rollczi.litecommands.argument.ArgumentKey;
import dev.rollczi.litecommands.argument.MutableArgument;

public class KeyAnnotationResolver<SENDER> implements AnnotationProcessor<SENDER> {

@Override
public AnnotationInvoker<SENDER> process(AnnotationInvoker<SENDER> invoker) {
return invoker.on(Key.class, (annotation, metaHolder) -> {
metaHolder.meta().edit(Meta.ARGUMENT_KEY, argumentKey -> argumentKey.withKey(annotation.value()));
return invoker.onParameterRequirement(Key.class, (parameter, key, builder, requirement) -> {
if (!(requirement instanceof MutableArgument<?>)) {
throw new IllegalArgumentException("@Key annotation can be used only on arguments: " + requirement.getClass().getName());
}

MutableArgument<?> argument = (MutableArgument<?>) requirement;
ArgumentKey argumentKey = argument.getKey();

argument.setKey(argumentKey.withKey(key.value()));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dev.rollczi.litecommands.annotations.AnnotationInvoker;
import dev.rollczi.litecommands.annotations.AnnotationProcessor;
import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.argument.MutableArgument;
import dev.rollczi.litecommands.argument.profile.ArgumentProfile;
import dev.rollczi.litecommands.reflect.LiteCommandsReflectInvocationException;
import java.lang.annotation.Annotation;
Expand All @@ -21,19 +22,19 @@ protected ProfileAnnotationProcessor(Class<A> annotationType) {
@Override
public AnnotationInvoker<SENDER> process(AnnotationInvoker<SENDER> invoker) {
return invoker.onParameterRequirement(annotationType, (parameter, annotation, builder, requirement) -> {
if (!(requirement instanceof Argument)) {
if (!(requirement instanceof MutableArgument)) {
Executable declaringExecutable = parameter.getDeclaringExecutable();
throw new LiteCommandsReflectInvocationException(declaringExecutable, parameter, "@" + annotationType.getSimpleName() + " can be used only on arguments");
throw new LiteCommandsReflectInvocationException(declaringExecutable, parameter, "@" + annotationType.getSimpleName() + " can be used only on arguments: " + requirement.getClass().getName());
}

Argument<?> argument = (Argument<?>) requirement;
MutableArgument<?> argument = (MutableArgument<?>) requirement;
PROFILE profile = createProfile(parameter, annotation, argument);

if (profile == null) {
return;
}

argument.withProfile(profile);
argument.addProfile(profile);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dev.rollczi.litecommands.annotations.async;

import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.programmatic.LiteCommand;
import static dev.rollczi.litecommands.programmatic.LiteProgrammatic.async;
import dev.rollczi.litecommands.scheduler.SchedulerExecutorPoolImpl;
import dev.rollczi.litecommands.unit.AssertExecute;
import dev.rollczi.litecommands.unit.LiteCommandsTestFactory;
import dev.rollczi.litecommands.unit.TestPlatform;
import dev.rollczi.litecommands.unit.TestSender;
import dev.rollczi.litecommands.util.FutureUtil;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class AsyncArgumentsInitializeTest {

private final static List<Class<?>> ARGUMENTS_TYPES = Arrays.asList(
String.class,
Integer.class,
Long.class,
Double.class,
Float.class,
Byte.class,
Short.class,
Boolean.class,
Duration.class,
Period.class,
Instant.class,
LocalDateTime.class
);

@Test
@DisplayName("Test for async arguments parsers initialization, should not throw any exceptions while running")
void test() {
LiteCommand<TestSender> command = new LiteCommand<>("test");

for (Class<?> type : ARGUMENTS_TYPES) {
command.argument(async(Argument.of(type.getSimpleName(), type)));
}

List<CompletableFuture<AssertExecute>> futures = new ArrayList<>();
for (int i = 0; i < 50; i++) {
TestPlatform platform = LiteCommandsTestFactory.startPlatform(config -> config
.scheduler(new SchedulerExecutorPoolImpl("lite-commands", 10))
.commands(command)
);

for (int j = 0; j < 10; j++) {
futures.add(platform.executeAsync("test test 1 100 1.0 1.0f 1 1 true 1s 1d 2021-01-01 00:00:00 2021-01-01 00:00:00"));
}
}

List<AssertExecute> executes = FutureUtil.asList(futures).join();

assertThat(executes)
.hasSize(500)
.allSatisfy(assertExecute -> assertExecute.assertSuccess());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
public class ChatGptArgument extends SimpleArgument<String> {

public ChatGptArgument(String name, String topic) {
super(name, TypeToken.of(String.class), false);
this.withProfile(new ChatGptArgumentProfile(topic));
this.withProfile(new JoinProfile());
super(name, TypeToken.of(String.class));
this.addProfile(new ChatGptArgumentProfile(topic));
this.addProfile(new JoinProfile());
}

public ChatGptArgument(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dev.rollczi.litecommands.argument.profile.ArgumentProfile;
import dev.rollczi.litecommands.argument.parser.ParseResult;
import dev.rollczi.litecommands.argument.profile.ArgumentProfileNamespace;
import dev.rollczi.litecommands.priority.PrioritizedList;
import dev.rollczi.litecommands.reflect.type.TypeToken;
import dev.rollczi.litecommands.requirement.Requirement;

Expand All @@ -25,26 +26,32 @@ default Optional<ParseResult<T>> defaultValue() {
boolean hasDefaultValue();

@ApiStatus.Experimental
<P extends ArgumentProfile<P>> Argument<T> withProfile(P profile);
<P> Optional<P> getProfile(ArgumentProfileNamespace<P> key);

@ApiStatus.Experimental
<P> Optional<P> getProfile(ArgumentProfileNamespace<P> key);
PrioritizedList<ArgumentProfile<?>> getProfiles();

/**
* Create a child of the current argument.
* This is useful when resolver handles a parametrized type for class such as Optional, List, CompletableFuture, etc.
*/
@ApiStatus.Experimental
<NEW> Argument<NEW> withType(TypeToken<NEW> type);
<NEW> Argument<NEW> child(TypeToken<NEW> type);

static <T> Argument<T> of(String name, Class<T> type) {
return new SimpleArgument<>(name, TypeToken.of(type), false);
return new SimpleArgument<>(name, TypeToken.of(type));
}

@Deprecated
static <T> Argument<T> of(String name, Class<T> type, boolean nullable) {
return new SimpleArgument<>(name, TypeToken.of(type), nullable);
}

static <T> Argument<T> of(String name, TypeToken<T> type) {
return new SimpleArgument<>(name, type, false);
return new SimpleArgument<>(name, type);
}

@Deprecated
static <T> Argument<T> of(String name, TypeToken<T> type, boolean nullable) {
return new SimpleArgument<>(name, type, nullable);
}
Expand All @@ -56,8 +63,8 @@ static <T, P extends ArgumentProfile<P>> Argument<T> profiled(String name, Class

@ApiStatus.Experimental
static <T, P extends ArgumentProfile<P>> Argument<T> profiled(String name, TypeToken<T> type, P profile) {
return new SimpleArgument<>(name, type, false)
.withProfile(profile);
return new SimpleArgument<>(name, type)
.addProfile(profile);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.rollczi.litecommands.argument;

import dev.rollczi.litecommands.argument.profile.ArgumentProfile;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public interface MutableArgument<T> extends Argument<T> {

@ApiStatus.Experimental
@ApiStatus.Internal
<P extends ArgumentProfile<P>> Argument<T> addProfile(P profile);

@ApiStatus.Internal
void setKey(ArgumentKey key);

}
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
package dev.rollczi.litecommands.argument;

import dev.rollczi.litecommands.argument.parser.ParseResult;
import dev.rollczi.litecommands.argument.profile.ArgumentProfile;
import dev.rollczi.litecommands.argument.profile.ArgumentProfileNamespace;
import dev.rollczi.litecommands.argument.parser.ParseResult;
import dev.rollczi.litecommands.meta.Meta;
import dev.rollczi.litecommands.meta.MetaHolder;
import dev.rollczi.litecommands.priority.MutablePrioritizedList;
import dev.rollczi.litecommands.priority.Prioritized;
import dev.rollczi.litecommands.priority.PrioritizedList;
import dev.rollczi.litecommands.reflect.type.TypeToken;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;
import java.util.Optional;

public class SimpleArgument<T> implements Argument<T> {
public class SimpleArgument<T> implements MutableArgument<T> {

private final String name;
private final TypeToken<T> type;
private final Meta meta = Meta.create();

private ArgumentKey key;
private volatile int hashCode;

private @Nullable Argument<?> pattern;

private final MutablePrioritizedList<ArgumentProfile<?>> profiles = new MutablePrioritizedList<>();

@Deprecated
private final boolean nullable;
private final MutablePrioritizedList<ArgumentProfile<?>> profiles = new MutablePrioritizedList<>();

public SimpleArgument(String name, TypeToken<T> type, @Deprecated boolean nullable) {
this.name = name;
this.type = type;
this.nullable = nullable;
this.meta.put(Meta.ARGUMENT_KEY, ArgumentKey.of(this.getClass().getName(), name));
protected SimpleArgument(String name, TypeToken<T> type, @Nullable Argument<?> pattern) {
this(name, type);
this.pattern = pattern;
}

public SimpleArgument(String name, TypeToken<T> type) {
this(name, type, false);
}

public SimpleArgument(String name, TypeToken<T> type, @Deprecated boolean nullable) {
this.name = name;
this.type = type;
this.nullable = false;
this.meta.put(Meta.ARGUMENT_KEY, ArgumentKey.of(this.getClass().getName(), name));
this.nullable = nullable;
this.setKey(ArgumentKey.of(this.getClass().getName(), name));
}

@Override
Expand All @@ -43,7 +53,13 @@ public String getName() {

@Override
public ArgumentKey getKey() {
return meta.get(Meta.ARGUMENT_KEY);
return key;
}

@Override
public void setKey(ArgumentKey key) {
this.key = key;
this.hashCode = createHashCode();
}

@Override
Expand All @@ -58,7 +74,7 @@ public Meta meta() {

@Override
public @Nullable MetaHolder parentMeta() {
return null;
return pattern;
}

@Override
Expand All @@ -76,15 +92,14 @@ public boolean hasDefaultValue() {
}

@ApiStatus.Experimental
public <P extends ArgumentProfile<P>> SimpleArgument<T> withProfile(P profile) {
public <P extends ArgumentProfile<P>> SimpleArgument<T> addProfile(P profile) {
ArgumentProfileNamespace<P> namespace = profile.getNamespace();

this.profiles.add(profile);
this.meta.put(namespace.asMetaKey(), profile);

if (this.profiles.first().equals(profile)) {
this.meta.edit(Meta.ARGUMENT_KEY, argumentKey -> argumentKey.withNamespace(namespace.getNamespace()));

this.setKey(this.key.withNamespace(namespace.getNamespace()));
}
return this;
}
Expand All @@ -96,10 +111,30 @@ public <P> Optional<P> getProfile(ArgumentProfileNamespace<P> key) {
}

@Override
public <NEW> Argument<NEW> withType(TypeToken<NEW> type) {
Argument<NEW> argument = Argument.of(name, type, nullable);
argument.meta().putAll(meta);
return argument;
public PrioritizedList<ArgumentProfile<?>> getProfiles() {
return profiles;
}

@Override
public <NEW> Argument<NEW> child(TypeToken<NEW> type) {
return new SimpleArgument<>(name, type, this);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SimpleArgument<?> that = (SimpleArgument<?>) o;
return Objects.equals(getKey(), that.getKey()) && Objects.equals(type, that.type);
}

@Override
public int hashCode() {
return hashCode;
}

private int createHashCode() {
return Objects.hash(getKey(), type);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dev.rollczi.litecommands.argument.ArgumentKey;
import dev.rollczi.litecommands.invocation.Invocation;
import dev.rollczi.litecommands.reflect.type.TypeRange;
import org.jetbrains.annotations.ApiStatus;

public interface ParserRegistry<SENDER> {

Expand All @@ -16,6 +17,7 @@ default <PARSED> void registerParser(Class<PARSED> parserType, ArgumentKey key,

<PARSED> void registerParser(TypeRange<PARSED> typeRange, ArgumentKey key, ParserChained<SENDER, PARSED> parser);

@ApiStatus.Internal
<PARSED> ParserSet<SENDER, PARSED> getParserSet(Class<PARSED> parsedClass, ArgumentKey key);

@Deprecated
Expand Down
Loading

0 comments on commit 4ea68bc

Please sign in to comment.