Use static empty context set where appropriate, cache reversed comparator instances

This commit is contained in:
Luck 2017-12-07 19:49:04 +00:00
parent 2baea0ce13
commit a5e41e479a
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
14 changed files with 200 additions and 78 deletions

View File

@ -163,27 +163,28 @@ public final class ImmutableContextSet implements ContextSet {
return true;
}
@Nonnull
@Override
@Deprecated // This set is already immutable!
@Nonnull
public ImmutableContextSet makeImmutable() {
return this;
}
@Override
@Nonnull
@Override
public MutableContextSet mutableCopy() {
return MutableContextSet.fromSet(this);
}
@Override
@Nonnull
@Override
public Set<Map.Entry<String, String>> toSet() {
return map.entries();
}
@Override
@Nonnull
@Override
@Deprecated
public Map<String, String> toMap() {
ImmutableMap.Builder<String, String> m = ImmutableMap.builder();
for (Map.Entry<String, String> e : map.entries()) {
@ -193,33 +194,33 @@ public final class ImmutableContextSet implements ContextSet {
return m.build();
}
@Override
@Nonnull
@Override
public Multimap<String, String> toMultimap() {
return map;
}
@Override
@Nonnull
@Override
public boolean containsKey(@Nonnull String key) {
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Override
@Nonnull
@Override
public Set<String> getValues(@Nonnull String key) {
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Override
@Nonnull
@Override
public boolean has(@Nonnull String key, @Nonnull String value) {
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Override
@Nonnull
@Override
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());

View File

@ -198,6 +198,7 @@ public final class MutableContextSet implements ContextSet {
@Nonnull
@Override
@Deprecated
public Map<String, String> toMap() {
ImmutableMap.Builder<String, String> m = ImmutableMap.builder();
for (Map.Entry<String, String> e : map.entries()) {

View File

@ -45,7 +45,7 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter {
@Getter
@Accessors(fluent = true)
private ExecutorService asyncLp;
private ExecutorService asyncFallback;
@Getter
@Accessors(fluent = true)
@ -61,17 +61,17 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter {
@Getter
@Setter
private boolean useBukkitAsync = false;
private boolean useFallback = true;
private final Set<BukkitTask> tasks = ConcurrentHashMap.newKeySet();
public BukkitSchedulerAdapter(LPBukkitPlugin plugin) {
this.plugin = plugin;
this.asyncLp = Executors.newCachedThreadPool();
this.asyncBukkit = r -> plugin.getServer().getScheduler().runTaskAsynchronously(plugin, r);
this.sync = r -> plugin.getServer().getScheduler().runTask(plugin, r);
this.async = r -> (useBukkitAsync ? asyncBukkit : asyncLp).execute(r);
this.sync = new SyncExecutor();
this.asyncFallback = Executors.newCachedThreadPool();
this.asyncBukkit = new BukkitAsyncExecutor();
this.async = new AsyncExecutor();
}
@Override
@ -103,19 +103,45 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter {
@Override
public void syncLater(Runnable runnable, long delayTicks) {
plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delayTicks);
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, runnable, delayTicks);
}
@Override
public void shutdown() {
tasks.forEach(BukkitTask::cancel);
// wait for executor
asyncLp.shutdown();
asyncFallback.shutdown();
try {
asyncLp.awaitTermination(30, TimeUnit.SECONDS);
asyncFallback.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private final class SyncExecutor implements Executor {
@Override
public void execute(Runnable runnable) {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, runnable);
}
}
private final class AsyncExecutor implements Executor {
@Override
public void execute(Runnable runnable) {
if (useFallback || !plugin.isEnabled()) {
asyncFallback.execute(runnable);
} else {
asyncBukkit.execute(runnable);
}
}
}
private final class BukkitAsyncExecutor implements Executor {
@Override
public void execute(Runnable runnable) {
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable);
}
}
}

View File

@ -294,7 +294,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
// replace the temporary executor when the Bukkit one starts
getServer().getScheduler().runTaskAsynchronously(this, () -> scheduler.setUseBukkitAsync(true));
getServer().getScheduler().runTaskAsynchronously(this, () -> scheduler.setUseFallback(false));
// Load any online users (in the case of a reload)
for (Player player : getServer().getOnlinePlayers()) {
@ -323,8 +323,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
return;
}
// Switch back to the LP executor, the bukkit one won't allow new tasks
scheduler.setUseBukkitAsync(false);
// Switch back to the fallback executor, the bukkit one won't allow new tasks
scheduler.setUseFallback(true);
started = false;

View File

@ -40,7 +40,7 @@ public class MetaComparator implements Comparator<Map.Entry<Integer, LocalizedNo
if (result != 0) {
return result;
}
return NodeWithContextComparator.get().compare(o1.getValue(), o2.getValue());
return NodeWithContextComparator.normal().compare(o1.getValue(), o2.getValue());
}
}

View File

@ -35,13 +35,15 @@ import java.util.Map;
public class ContextSetComparator implements Comparator<ImmutableContextSet> {
private static final ContextSetComparator INSTANCE = new ContextSetComparator();
public static Comparator<ImmutableContextSet> get() {
private static final Comparator<ImmutableContextSet> INSTANCE = new ContextSetComparator();
private static final Comparator<ImmutableContextSet> REVERSE = INSTANCE.reversed();
public static Comparator<ImmutableContextSet> normal() {
return INSTANCE;
}
public static Comparator<ImmutableContextSet> reverse() {
return INSTANCE.reversed();
return REVERSE;
}
@Override

View File

@ -61,10 +61,14 @@ public class ContextSetConfigurateSerializer {
return data;
}
public static MutableContextSet deserializeContextSet(ConfigurationNode data) {
public static ContextSet deserializeContextSet(ConfigurationNode data) {
Preconditions.checkArgument(data.hasMapChildren());
Map<Object, ? extends ConfigurationNode> dataMap = data.getChildrenMap();
if (dataMap.isEmpty()) {
return ContextSet.empty();
}
MutableContextSet map = MutableContextSet.create();
for (Map.Entry<Object, ? extends ConfigurationNode> e : dataMap.entrySet()) {
String k = e.getKey().toString();

View File

@ -28,6 +28,7 @@ package me.lucko.luckperms.common.contexts;
import lombok.experimental.UtilityClass;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ -66,10 +67,28 @@ public class ContextSetJsonSerializer {
return data;
}
public static MutableContextSet deserializeContextSet(JsonElement element) {
public static ContextSet deserializeContextSet(Gson gson, String json) {
Preconditions.checkNotNull(json, "json");
if (json.equals("{}")) {
return ContextSet.empty();
}
JsonObject context = gson.fromJson(json, JsonObject.class);
if (context == null || context.size() == 0) {
return ContextSet.empty();
}
return deserializeContextSet(context);
}
public static ContextSet deserializeContextSet(JsonElement element) {
Preconditions.checkArgument(element.isJsonObject());
JsonObject data = element.getAsJsonObject();
if (data.size() == 0) {
return ContextSet.empty();
}
MutableContextSet map = MutableContextSet.create();
for (Map.Entry<String, JsonElement> e : data.entrySet()) {
String k = e.getKey();

View File

@ -26,17 +26,21 @@
package me.lucko.luckperms.common.node;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.utils.CollationKeyCache;
import java.util.Comparator;
public class NodeComparator implements Comparator<Node> {
private static final NodeComparator INSTANCE = new NodeComparator();
public static Comparator<Node> get() {
private static final Comparator<Node> INSTANCE = new NodeComparator();
private static final Comparator<Node> REVERSE = INSTANCE.reversed();
public static Comparator<Node> normal() {
return INSTANCE;
}
public static Comparator<Node> reverse() {
return INSTANCE.reversed();
return REVERSE;
}
@Override
@ -61,7 +65,7 @@ public class NodeComparator implements Comparator<Node> {
return o1.getWildcardLevel() > o2.getWildcardLevel() ? 1 : -1;
}
return NodeWithContextComparator.get().compareStrings(o1.getPermission(), o2.getPermission()) == 1 ? -1 : 1;
return CollationKeyCache.compareStrings(o1.getPermission(), o2.getPermission()) == 1 ? -1 : 1;
}
}

View File

@ -74,13 +74,13 @@ public final class NodeModel {
public synchronized Node toNode() {
if (node == null) {
Node.Builder builder = NodeFactory.newBuilder(permission);
builder.setValue(value);
builder.setServer(server);
builder.setWorld(world);
builder.setExpiry(expiry);
builder.withExtraContext(contexts);
node = builder.build();
node = NodeFactory.newBuilder(permission)
.setValue(value)
.setServer(server)
.setWorld(world)
.setExpiry(expiry)
.withExtraContext(contexts)
.build();
}
return node;

View File

@ -28,16 +28,11 @@ package me.lucko.luckperms.common.node;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.api.LocalizedNode;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.utils.CollationKeyCache;
import java.text.CollationKey;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
/**
* Compares permission nodes based upon their supposed "priority".
@ -45,16 +40,16 @@ import java.util.Locale;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NodeWithContextComparator implements Comparator<LocalizedNode> {
private static final NodeWithContextComparator INSTANCE = new NodeWithContextComparator();
public static NodeWithContextComparator get() {
private static final Comparator<LocalizedNode> INSTANCE = new NodeWithContextComparator();
private static final Comparator<LocalizedNode> REVERSE = INSTANCE.reversed();
public static Comparator<LocalizedNode> normal() {
return INSTANCE;
}
public static Comparator<LocalizedNode> reverse() {
return INSTANCE.reversed();
}
private final Collator collator = Collator.getInstance(Locale.ENGLISH);
private final LoadingCache<String, CollationKey> collationKeyCache = Caffeine.newBuilder().build(collator::getCollationKey);
public static Comparator<LocalizedNode> reverse() {
return REVERSE;
}
@Override
public int compare(LocalizedNode one, LocalizedNode two) {
@ -97,27 +92,8 @@ public class NodeWithContextComparator implements Comparator<LocalizedNode> {
return o1.getWildcardLevel() > o2.getWildcardLevel() ? 1 : -1;
}
return compareStrings(o1.getPermission(), o2.getPermission()) == 1 ? -1 : 1;
return CollationKeyCache.compareStrings(o1.getPermission(), o2.getPermission()) == 1 ? -1 : 1;
}
public int compareStrings(String o1, String o2) {
if (o1.equals(o2)) {
return 0;
}
try {
CollationKey o1c = collationKeyCache.get(o1);
CollationKey o2c = collationKeyCache.get(o2);
int i = o1c.compareTo(o2c);
if (i != 0) {
return i;
}
// fallback to standard string comparison
return o1.compareTo(o2);
} catch (Exception e) {
// ignored
}
return 1;
}
}

View File

@ -30,7 +30,6 @@ import lombok.Getter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.api.HeldPermission;
@ -1129,7 +1128,6 @@ public class SqlDao extends AbstractDao {
}
private NodeModel deserializeNode(String permission, boolean value, String server, String world, long expiry, String contexts) {
JsonObject context = gson.fromJson(contexts, JsonObject.class);
return NodeModel.of(permission, value, server, world, expiry, ContextSetJsonSerializer.deserializeContextSet(context).makeImmutable());
return NodeModel.of(permission, value, server, world, expiry, ContextSetJsonSerializer.deserializeContextSet(gson, contexts).makeImmutable());
}
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.utils;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.text.CollationKey;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
public final class CollationKeyCache implements Comparator<String> {
private static final CollationKeyCache INSTANCE = new CollationKeyCache();
private static final Collator COLLATOR = Collator.getInstance(Locale.ENGLISH);
static {
COLLATOR.setStrength(Collator.IDENTICAL);
COLLATOR.setDecomposition(Collator.FULL_DECOMPOSITION);
}
private static final LoadingCache<String, CollationKey> CACHE = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(COLLATOR::getCollationKey);
public static Comparator<String> comparator() {
return INSTANCE;
}
private CollationKeyCache() {
}
@Override
public int compare(String o1, String o2) {
return compareStrings(o1, o2);
}
public static int compareStrings(String o1, String o2) {
//noinspection StringEquality
if (o1 == o2) {
return 0;
}
try {
CollationKey o1c = CACHE.get(o1);
CollationKey o2c = CACHE.get(o2);
if (o1c != null && o2c != null) {
int i = o1c.compareTo(o2c);
if (i != 0) {
return i;
}
}
// fallback to standard string comparison
return o1.compareTo(o2);
} catch (Exception e) {
// ignored
}
// shrug
return 0;
}
}

View File

@ -37,7 +37,7 @@ import com.google.gson.JsonObject;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.contexts.ContextSetComparator;
import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import me.lucko.luckperms.common.node.NodeWithContextComparator;
import me.lucko.luckperms.common.utils.CollationKeyCache;
import me.lucko.luckperms.sponge.service.calculated.CalculatedSubjectData;
import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
@ -195,7 +195,7 @@ public class SubjectStorageModel {
// sort alphabetically.
List<Map.Entry<String, Boolean>> perms = new ArrayList<>(e.getValue().entrySet());
perms.sort((o1, o2) -> NodeWithContextComparator.get().compareStrings(o1.getKey(), o2.getKey()));
perms.sort(Map.Entry.comparingByKey(CollationKeyCache.comparator()));
for (Map.Entry<String, Boolean> ent : perms) {
data.addProperty(ent.getKey(), ent.getValue());
@ -219,7 +219,7 @@ public class SubjectStorageModel {
// sort alphabetically.
List<Map.Entry<String, String>> opts = new ArrayList<>(e.getValue().entrySet());
opts.sort((o1, o2) -> NodeWithContextComparator.get().compareStrings(o1.getKey(), o2.getKey()));
opts.sort(Map.Entry.comparingByKey(CollationKeyCache.comparator()));
for (Map.Entry<String, String> ent : opts) {
data.addProperty(ent.getKey(), ent.getValue());