Merge pull request 'Add TabCompletionCache' (#9) from Caching into master
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful
Reviewed-on: #9
Dieser Commit ist enthalten in:
Commit
21e77c55f0
@ -23,6 +23,7 @@ import java.lang.annotation.*;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.IntPredicate;
|
import java.util.function.IntPredicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@ -108,7 +109,9 @@ public abstract class AbstractSWCommand<T> {
|
|||||||
private synchronized void createMapping() {
|
private synchronized void createMapping() {
|
||||||
List<Method> methods = methods();
|
List<Method> methods = methods();
|
||||||
for (Method method : methods) {
|
for (Method method : methods) {
|
||||||
|
Cached cached = method.getAnnotation(Cached.class);
|
||||||
addMapper(Mapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> {
|
addMapper(Mapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> {
|
||||||
|
TabCompletionCache.add(typeMapper, cached);
|
||||||
if (anno.local()) {
|
if (anno.local()) {
|
||||||
localTypeMapper.putIfAbsent(anno.value(), (AbstractTypeMapper<T, ?>) typeMapper);
|
localTypeMapper.putIfAbsent(anno.value(), (AbstractTypeMapper<T, ?>) typeMapper);
|
||||||
} else {
|
} else {
|
||||||
@ -116,6 +119,7 @@ public abstract class AbstractSWCommand<T> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
addMapper(ClassMapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> {
|
addMapper(ClassMapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> {
|
||||||
|
TabCompletionCache.add(typeMapper, cached);
|
||||||
if (anno.local()) {
|
if (anno.local()) {
|
||||||
localTypeMapper.putIfAbsent(anno.value().getTypeName(), (AbstractTypeMapper<T, ?>) typeMapper);
|
localTypeMapper.putIfAbsent(anno.value().getTypeName(), (AbstractTypeMapper<T, ?>) typeMapper);
|
||||||
} else {
|
} else {
|
||||||
@ -315,6 +319,14 @@ public abstract class AbstractSWCommand<T> {
|
|||||||
boolean local() default false;
|
boolean local() default false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
protected @interface Cached {
|
||||||
|
long cacheDuration() default 5;
|
||||||
|
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||||
|
boolean global() default false;
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||||
|
@ -169,7 +169,7 @@ class CommandPart<T> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Collection<String> strings = typeMapper.tabCompletes(sender, Arrays.copyOf(args, args.length - 1), args[args.length - 1]);
|
Collection<String> strings = tabCompletes(sender, args, args.length - 1);
|
||||||
if (strings != null) {
|
if (strings != null) {
|
||||||
current.addAll(strings);
|
current.addAll(strings);
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ class CommandPart<T> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<String> strings = typeMapper.tabCompletes(sender, Arrays.copyOf(args, startIndex), args[startIndex]);
|
Collection<String> strings = tabCompletes(sender, args, startIndex);
|
||||||
if (strings != null) {
|
if (strings != null) {
|
||||||
current.addAll(strings);
|
current.addAll(strings);
|
||||||
}
|
}
|
||||||
@ -197,6 +197,12 @@ class CommandPart<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Collection<String> tabCompletes(T sender, String[] args, int startIndex) {
|
||||||
|
return TabCompletionCache.tabComplete(sender, typeMapper, command, () -> {
|
||||||
|
return typeMapper.tabCompletes(sender, Arrays.copyOf(args, startIndex), args[startIndex]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private CheckArgumentResult checkArgument(Consumer<Runnable> errors, GuardCheckType guardCheckType, T sender, String[] args, int index) {
|
private CheckArgumentResult checkArgument(Consumer<Runnable> errors, GuardCheckType guardCheckType, T sender, String[] args, int index) {
|
||||||
try {
|
try {
|
||||||
Object value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]);
|
Object value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]);
|
||||||
|
@ -313,7 +313,7 @@ public class SWCommandUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static <T extends Annotation> T[] getAnnotation(Method method, Class<T> annotation) {
|
static <T extends Annotation> T[] getAnnotation(Method method, Class<T> annotation) {
|
||||||
if (method.getAnnotations().length != 1) return null;
|
if (method.getAnnotations().length == 0) return null;
|
||||||
return method.getDeclaredAnnotationsByType(annotation);
|
return method.getDeclaredAnnotationsByType(annotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
src/de/steamwar/command/TabCompletionCache.java
Normale Datei
84
src/de/steamwar/command/TabCompletionCache.java
Normale Datei
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class TabCompletionCache {
|
||||||
|
|
||||||
|
private Map<Key, TabCompletions> tabCompletionCache = new HashMap<>();
|
||||||
|
Set<AbstractTypeMapper<?, ?>> cached = new HashSet<>();
|
||||||
|
Set<AbstractTypeMapper<?, ?>> global = new HashSet<>();
|
||||||
|
Map<AbstractTypeMapper<?, ?>, Long> cacheDuration = new HashMap<>();
|
||||||
|
|
||||||
|
void add(AbstractTypeMapper<?, ?> typeMapper, AbstractSWCommand.Cached cached) {
|
||||||
|
if (cached != null) {
|
||||||
|
TabCompletionCache.cached.add(typeMapper);
|
||||||
|
if (cached.global()) TabCompletionCache.global.add(typeMapper);
|
||||||
|
TabCompletionCache.cacheDuration.put(typeMapper, cached.timeUnit().toMillis(cached.cacheDuration()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@AllArgsConstructor
|
||||||
|
private static class Key {
|
||||||
|
private Object sender;
|
||||||
|
private AbstractTypeMapper<?, ?> typeMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
private static class TabCompletions {
|
||||||
|
private AbstractSWCommand<?> command;
|
||||||
|
private long timestamp;
|
||||||
|
private Collection<String> tabCompletions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<String> tabComplete(Object sender, AbstractTypeMapper<?, ?> typeMapper, AbstractSWCommand<?> command, Supplier<Collection<String>> tabCompleteSupplier) {
|
||||||
|
if (!cached.contains(typeMapper)) return tabCompleteSupplier.get();
|
||||||
|
Key key = global.contains(typeMapper) ? new Key(null, typeMapper) : new Key(sender, typeMapper);
|
||||||
|
TabCompletions tabCompletions = tabCompletionCache.computeIfAbsent(key, ignore -> {
|
||||||
|
return new TabCompletions(command, System.currentTimeMillis(), tabCompleteSupplier.get());
|
||||||
|
});
|
||||||
|
if (tabCompletions.command != command || System.currentTimeMillis() - tabCompletions.timestamp > cacheDuration.get(typeMapper)) {
|
||||||
|
tabCompletions = new TabCompletions(command, System.currentTimeMillis(), tabCompleteSupplier.get());
|
||||||
|
tabCompletionCache.put(key, tabCompletions);
|
||||||
|
}
|
||||||
|
tabCompletions.timestamp = System.currentTimeMillis();
|
||||||
|
return tabCompletions.tabCompletions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invalidateOldEntries() {
|
||||||
|
Set<Key> toRemove = new HashSet<>();
|
||||||
|
for (Map.Entry<Key, TabCompletions> tabCompletionsEntry : tabCompletionCache.entrySet()) {
|
||||||
|
if (System.currentTimeMillis() - tabCompletionsEntry.getValue().timestamp > cacheDuration.get(tabCompletionsEntry.getKey().typeMapper)) {
|
||||||
|
toRemove.add(tabCompletionsEntry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Key key : toRemove) {
|
||||||
|
tabCompletionCache.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
testsrc/de/steamwar/command/CacheCommand.java
Normale Datei
57
testsrc/de/steamwar/command/CacheCommand.java
Normale Datei
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import de.steamwar.command.dto.TestSWCommand;
|
||||||
|
import de.steamwar.command.dto.TestTypeMapper;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class CacheCommand extends TestSWCommand {
|
||||||
|
|
||||||
|
public CacheCommand() {
|
||||||
|
super("typemapper");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Register
|
||||||
|
public void test(String sender, int tabCompleteTest) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtomicInteger count = new AtomicInteger();
|
||||||
|
|
||||||
|
@Cached
|
||||||
|
@Mapper(value = "int", local = true)
|
||||||
|
public AbstractTypeMapper<String, Integer> typeMapper() {
|
||||||
|
System.out.println("TypeMapper register");
|
||||||
|
return new TestTypeMapper<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer map(String sender, String[] previousArguments, String s) {
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> tabCompletes(String sender, String[] previousArguments, String s) {
|
||||||
|
return Arrays.asList(count.getAndIncrement() + "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
46
testsrc/de/steamwar/command/CacheCommandTest.java
Normale Datei
46
testsrc/de/steamwar/command/CacheCommandTest.java
Normale Datei
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
public class CacheCommandTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCaching() {
|
||||||
|
CacheCommand cmd = new CacheCommand();
|
||||||
|
List<String> tabCompletions1 = cmd.tabComplete("test", "", new String[]{""});
|
||||||
|
List<String> tabCompletions2 = cmd.tabComplete("test", "", new String[]{""});
|
||||||
|
assertThat(tabCompletions1, is(equalTo(tabCompletions2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCachingWithDifferentSenders() {
|
||||||
|
CacheCommand cmd = new CacheCommand();
|
||||||
|
List<String> tabCompletions1 = cmd.tabComplete("test", "", new String[]{""});
|
||||||
|
List<String> tabCompletions2 = cmd.tabComplete("test2", "", new String[]{""});
|
||||||
|
assertThat(tabCompletions1, is(not(equalTo(tabCompletions2))));
|
||||||
|
}
|
||||||
|
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren