Implement nicer json format for Sponge local data

This commit is contained in:
Luck 2017-03-19 17:54:12 +00:00
parent 85c7a7db8d
commit 073b775566
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
11 changed files with 498 additions and 140 deletions

View File

@ -25,6 +25,7 @@ package me.lucko.luckperms.api.context;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
@ -179,7 +180,7 @@ public final class MutableContextSet implements ContextSet {
@Override
public Multimap<String, String> toMultimap() {
return map;
return ImmutableSetMultimap.copyOf(map);
}
@Override

View File

@ -30,6 +30,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.Node;
@ -547,11 +548,11 @@ public class JSONBacking extends FlatfileBacking {
int size = vals.size();
if (size == 1) {
context.addProperty(e.getKey(), vals.get(0));;
context.addProperty(e.getKey(), vals.get(0));
} else if (size > 1) {
JsonArray arr = new JsonArray();
for (String s : vals) {
arr.add(s);
arr.add(new JsonPrimitive(s));
}
context.add(e.getKey(), arr);
}

View File

@ -57,12 +57,13 @@ import me.lucko.luckperms.sponge.model.SpongeGroup;
import me.lucko.luckperms.sponge.service.calculated.CalculatedSubjectData;
import me.lucko.luckperms.sponge.service.calculated.OptionLookup;
import me.lucko.luckperms.sponge.service.calculated.PermissionLookup;
import me.lucko.luckperms.sponge.service.legacystorage.LegacyDataMigrator;
import me.lucko.luckperms.sponge.service.persisted.PersistedCollection;
import me.lucko.luckperms.sponge.service.persisted.SubjectStorage;
import me.lucko.luckperms.sponge.service.proxy.LPSubject;
import me.lucko.luckperms.sponge.service.proxy.LPSubjectCollection;
import me.lucko.luckperms.sponge.service.proxy.LPSubjectData;
import me.lucko.luckperms.sponge.service.references.SubjectReference;
import me.lucko.luckperms.sponge.service.storage.SubjectStorage;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.plugin.PluginContainer;
@ -129,7 +130,8 @@ public class LuckPermsService implements PermissionService {
localOptionCaches = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
localDataCaches = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
storage = new SubjectStorage(new File(plugin.getDataDirectory(), "local"));
storage = new SubjectStorage(new File(plugin.getDataDirectory(), "sponge-data"));
new LegacyDataMigrator(plugin, new File(plugin.getDataDirectory(), "local"), storage).run();
userSubjects = plugin.getUserManager();
fallbackUserSubjects = new PersistedCollection(this, "fallback-users", true);

View File

@ -47,6 +47,7 @@ import me.lucko.luckperms.sponge.service.references.SubjectReference;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -133,29 +134,29 @@ public class CalculatedSubjectData implements LPSubjectData {
return permissionCache.getUnchecked(contexts).getCalculator().getPermissionValue(permission);
}
public void replacePermissions(Map<ContextSet, Map<String, Boolean>> map) {
public void replacePermissions(Map<ImmutableContextSet, Map<String, Boolean>> map) {
permissions.clear();
for (Map.Entry<ContextSet, Map<String, Boolean>> e : map.entrySet()) {
permissions.put(e.getKey().makeImmutable(), new ConcurrentHashMap<>(e.getValue()));
for (Map.Entry<ImmutableContextSet, Map<String, Boolean>> e : map.entrySet()) {
permissions.put(e.getKey(), new ConcurrentHashMap<>(e.getValue()));
}
permissionCache.invalidateAll();
service.invalidatePermissionCaches();
}
public void replaceParents(Map<ContextSet, Set<SubjectReference>> map) {
public void replaceParents(Map<ImmutableContextSet, List<SubjectReference>> map) {
parents.clear();
for (Map.Entry<ContextSet, Set<SubjectReference>> e : map.entrySet()) {
for (Map.Entry<ImmutableContextSet, List<SubjectReference>> e : map.entrySet()) {
Set<SubjectReference> set = ConcurrentHashMap.newKeySet();
set.addAll(e.getValue());
parents.put(e.getKey().makeImmutable(), set);
parents.put(e.getKey(), set);
}
service.invalidateParentCaches();
}
public void replaceOptions(Map<ContextSet, Map<String, String>> map) {
public void replaceOptions(Map<ImmutableContextSet, Map<String, String>> map) {
options.clear();
for (Map.Entry<ContextSet, Map<String, String>> e : map.entrySet()) {
options.put(e.getKey().makeImmutable(), new ConcurrentHashMap<>(e.getValue()));
for (Map.Entry<ImmutableContextSet, Map<String, String>> e : map.entrySet()) {
options.put(e.getKey(), new ConcurrentHashMap<>(e.getValue()));
}
service.invalidateOptionCaches();
}
@ -228,6 +229,14 @@ public class CalculatedSubjectData implements LPSubjectData {
return map.build();
}
public Map<ImmutableContextSet, List<SubjectReference>> getParentsAsList() {
ImmutableMap.Builder<ImmutableContextSet, List<SubjectReference>> map = ImmutableMap.builder();
for (Map.Entry<ContextSet, Set<SubjectReference>> e : parents.entrySet()) {
map.put(e.getKey().makeImmutable(), ImmutableList.copyOf(e.getValue()));
}
return map.build();
}
@Override
public Set<SubjectReference> getParents(ContextSet contexts) {
return ImmutableSet.copyOf(parents.getOrDefault(contexts, ImmutableSet.of()));

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.sponge.service.legacystorage;
import lombok.RequiredArgsConstructor;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.sponge.service.storage.SubjectStorage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class LegacyDataMigrator implements Runnable {
private final LuckPermsPlugin plugin;
private final File oldDirectory;
private final SubjectStorage storage;
@Override
public void run() {
if (!oldDirectory.exists() || !oldDirectory.isDirectory()) {
return;
}
plugin.getLog().warn("Migrating old sponge data... Please wait.");
File[] collections = oldDirectory.listFiles(File::isDirectory);
if (collections == null) {
return;
}
for (File collectionDir : collections) {
File[] subjects = collectionDir.listFiles((dir, name) -> name.endsWith(".json"));
if (subjects == null) {
continue;
}
for (File subjectFile : subjects) {
String subjectName = subjectFile.getName().substring(0, subjectFile.getName().length() - ".json".length());
try (BufferedReader reader = Files.newBufferedReader(subjectFile.toPath(), StandardCharsets.UTF_8)) {
SubjectDataHolder holder = storage.getGson().fromJson(reader, SubjectDataHolder.class);
storage.saveToFile(holder.asSubjectModel(), storage.resolveFile(collectionDir.getName(), subjectName));
} catch (IOException e) {
e.printStackTrace();
}
subjectFile.delete();
}
collectionDir.delete();
}
oldDirectory.delete();
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.sponge.service.legacystorage;
import lombok.ToString;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.sponge.service.references.SubjectReference;
import me.lucko.luckperms.sponge.service.storage.SubjectStorageModel;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @deprecated Because this format is no longer being used to store data.
* @see SubjectStorageModel
*/
@ToString
@Deprecated
public class SubjectDataHolder {
private Map<Map<String, String>, Map<String, Boolean>> permissions;
private Map<Map<String, String>, Map<String, String>> options;
private Map<Map<String, String>, List<String>> parents;
public SubjectDataHolder() {
// For gson
}
public SubjectStorageModel asSubjectModel() {
return new SubjectStorageModel(
permissions.entrySet().stream()
.collect(Collectors.toMap(
k -> ContextSet.fromMap(k.getKey()),
Map.Entry::getValue
)),
options.entrySet().stream()
.collect(Collectors.toMap(
k -> ContextSet.fromMap(k.getKey()),
Map.Entry::getValue
)),
parents.entrySet().stream()
.collect(Collectors.toMap(
k -> ContextSet.fromMap(k.getKey()),
v -> v.getValue().stream().map(SubjectReference::deserialize).collect(Collectors.toList())
))
);
}
}

View File

@ -39,6 +39,7 @@ import me.lucko.luckperms.sponge.service.LuckPermsService;
import me.lucko.luckperms.sponge.service.proxy.LPSubject;
import me.lucko.luckperms.sponge.service.proxy.LPSubjectCollection;
import me.lucko.luckperms.sponge.service.references.SubjectReference;
import me.lucko.luckperms.sponge.service.storage.SubjectStorageModel;
import java.util.Collection;
import java.util.Map;
@ -63,8 +64,8 @@ public class PersistedCollection implements LPSubjectCollection {
});
public void loadAll() {
Map<String, SubjectDataHolder> holders = service.getStorage().loadAllFromFile(identifier);
for (Map.Entry<String, SubjectDataHolder> e : holders.entrySet()) {
Map<String, SubjectStorageModel> holders = service.getStorage().loadAllFromFile(identifier);
for (Map.Entry<String, SubjectStorageModel> e : holders.entrySet()) {
PersistedSubject subject = get(e.getKey());
subject.loadData(e.getValue());
}

View File

@ -41,6 +41,7 @@ import me.lucko.luckperms.sponge.service.calculated.PermissionLookup;
import me.lucko.luckperms.sponge.service.proxy.LPSubject;
import me.lucko.luckperms.sponge.service.references.SubjectCollectionReference;
import me.lucko.luckperms.sponge.service.references.SubjectReference;
import me.lucko.luckperms.sponge.service.storage.SubjectStorageModel;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.command.CommandSource;
@ -130,9 +131,9 @@ public class PersistedSubject implements LPSubject {
this.optionLookupCache.cleanUp();
}
public void loadData(SubjectDataHolder dataHolder) {
public void loadData(SubjectStorageModel dataHolder) {
subjectData.setSave(false);
dataHolder.copyTo(subjectData);
dataHolder.applyToData(subjectData);
subjectData.setSave(true);
}

View File

@ -1,96 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.sponge.service.persisted;
import lombok.ToString;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.sponge.service.calculated.CalculatedSubjectData;
import me.lucko.luckperms.sponge.service.references.SubjectReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Holds SubjectData in a "gson friendly" format for serialization
*/
@ToString
public class SubjectDataHolder {
private final Map<Map<String, String>, Map<String, Boolean>> permissions;
private final Map<Map<String, String>, Map<String, String>> options;
private final Map<Map<String, String>, List<String>> parents;
public SubjectDataHolder(Map<ImmutableContextSet, Map<String, String>> options, Map<ImmutableContextSet, Map<String, Boolean>> permissions, Map<ImmutableContextSet, Set<SubjectReference>> parents) {
this.options = new HashMap<>();
for (Map.Entry<ImmutableContextSet, Map<String, String>> e : options.entrySet()) {
if (!e.getValue().isEmpty()) {
this.options.put(e.getKey().toMap(), new HashMap<>(e.getValue()));
}
}
this.permissions = new HashMap<>();
for (Map.Entry<ImmutableContextSet, Map<String, Boolean>> e : permissions.entrySet()) {
if (!e.getValue().isEmpty()) {
this.permissions.put(e.getKey().toMap(), new HashMap<>(e.getValue()));
}
}
this.parents = new HashMap<>();
for (Map.Entry<ImmutableContextSet, Set<SubjectReference>> e : parents.entrySet()) {
if (!e.getValue().isEmpty()) {
this.parents.put(e.getKey().toMap(), e.getValue().stream().map(SubjectReference::serialize).collect(Collectors.toList()));
}
}
}
public SubjectDataHolder(CalculatedSubjectData data) {
this(data.getOptions(), data.getPermissions(), data.getParents());
}
public void copyTo(CalculatedSubjectData subjectData) {
subjectData.replacePermissions(permissions.entrySet().stream()
.collect(Collectors.toMap(
k -> ContextSet.fromMap(k.getKey()),
Map.Entry::getValue
))
);
subjectData.replaceOptions(options.entrySet().stream()
.collect(Collectors.toMap(
k -> ContextSet.fromMap(k.getKey()),
Map.Entry::getValue
))
);
subjectData.replaceParents(parents.entrySet().stream()
.collect(Collectors.toMap(
k -> ContextSet.fromMap(k.getKey()),
v -> v.getValue().stream().map(SubjectReference::deserialize).collect(Collectors.toSet())
))
);
}
}

View File

@ -20,17 +20,24 @@
* SOFTWARE.
*/
package me.lucko.luckperms.sponge.service.persisted;
package me.lucko.luckperms.sponge.service.storage;
import lombok.Getter;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import me.lucko.luckperms.sponge.service.persisted.PersistedSubject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.AbstractMap;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -41,11 +48,14 @@ import java.util.stream.Collectors;
* Handles persisted Subject I/O and (de)serialization
*/
public class SubjectStorage {
@Getter
private final Gson gson;
private final File container;
public SubjectStorage(File container) {
this.gson = new GsonBuilder().setPrettyPrinting().enableComplexMapKeySerialization().create();
this.gson = new GsonBuilder().setPrettyPrinting().create();
this.container = container;
checkContainer();
}
@ -65,32 +75,35 @@ public class SubjectStorage {
return ImmutableSet.copyOf(dirs).stream().map(File::getName).collect(Collectors.toSet());
}
public void saveToFile(PersistedSubject subject) throws IOException {
public File resolveFile(String collectionName, String subjectName) {
checkContainer();
File collection = new File(container, subject.getContainingCollection().getIdentifier());
File collection = new File(container, collectionName);
if (!collection.exists()) {
collection.mkdirs();
}
File subjectFile = new File(collection, subject.getIdentifier() + ".json");
saveToFile(subject, subjectFile);
return new File(collection, subjectName + ".json");
}
public void saveToFile(PersistedSubject subject, File file) throws IOException {
public void saveToFile(PersistedSubject subject) throws IOException {
File subjectFile = resolveFile(subject.getContainingCollection().getIdentifier(), subject.getIdentifier());
saveToFile(new SubjectStorageModel(subject.getSubjectData()), subjectFile);
}
public void saveToFile(SubjectStorageModel model, File file) throws IOException {
file.getParentFile().mkdirs();
if (file.exists()) {
file.delete();
}
file.createNewFile();
Files.write(saveToString(new SubjectDataHolder(subject.getSubjectData())), file, Charset.defaultCharset());
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
gson.toJson(model.toJson(), writer);
writer.flush();
}
}
public String saveToString(SubjectDataHolder subject) {
return gson.toJson(subject);
}
public Map<String, SubjectDataHolder> loadAllFromFile(String collectionName) {
public Map<String, SubjectStorageModel> loadAllFromFile(String collectionName) {
checkContainer();
File collection = new File(container, collectionName);
if (!collection.exists()) {
@ -100,12 +113,12 @@ public class SubjectStorage {
String[] fileNames = collection.list((dir, name) -> name.endsWith(".json"));
if (fileNames == null) return Collections.emptyMap();
Map<String, SubjectDataHolder> holders = new HashMap<>();
Map<String, SubjectStorageModel> holders = new HashMap<>();
for (String name : fileNames) {
File subject = new File(collection, name);
try {
Map.Entry<String, SubjectDataHolder> s = loadFromFile(subject);
Map.Entry<String, SubjectStorageModel> s = loadFromFile(subject);
if (s != null) {
holders.put(s.getKey(), s.getValue());
}
@ -117,7 +130,7 @@ public class SubjectStorage {
return holders;
}
public Map.Entry<String, SubjectDataHolder> loadFromFile(String collectionName, String subjectName) throws IOException {
public Map.Entry<String, SubjectStorageModel> loadFromFile(String collectionName, String subjectName) throws IOException {
checkContainer();
File collection = new File(container, collectionName);
if (!collection.exists()) {
@ -125,20 +138,21 @@ public class SubjectStorage {
}
File subject = new File(collection, subjectName + ".json");
return new AbstractMap.SimpleEntry<>(subjectName, loadFromFile(subject).getValue());
return Maps.immutableEntry(subjectName, loadFromFile(subject).getValue());
}
public Map.Entry<String, SubjectDataHolder> loadFromFile(File file) throws IOException {
public Map.Entry<String, SubjectStorageModel> loadFromFile(File file) throws IOException {
if (!file.exists()) {
return null;
}
String s = Files.toString(file, Charset.defaultCharset());
return new AbstractMap.SimpleEntry<>(file.getName().substring(0, file.getName().length() - 5), loadFromString(s));
}
String subjectName = file.getName().substring(0, file.getName().length() - ".json".length());
public SubjectDataHolder loadFromString(String s) {
return gson.fromJson(s, SubjectDataHolder.class);
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
JsonObject data = gson.fromJson(reader, JsonObject.class);
SubjectStorageModel model = new SubjectStorageModel(data);
return Maps.immutableEntry(subjectName, model);
}
}
}

View File

@ -0,0 +1,274 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.sponge.service.storage;
import lombok.Getter;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.sponge.service.calculated.CalculatedSubjectData;
import me.lucko.luckperms.sponge.service.references.SubjectReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Used for converting a SubjectData instance to and from JSON
*/
@Getter
public class SubjectStorageModel {
private final Map<ImmutableContextSet, Map<String, Boolean>> permissions;
private final Map<ImmutableContextSet, Map<String, String>> options;
private final Map<ImmutableContextSet, List<SubjectReference>> parents;
public SubjectStorageModel(Map<ImmutableContextSet, Map<String, Boolean>> permissions, Map<ImmutableContextSet, Map<String, String>> options, Map<ImmutableContextSet, List<SubjectReference>> parents) {
ImmutableMap.Builder<ImmutableContextSet, Map<String, Boolean>> permissionsBuilder = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, Map<String, Boolean>> e : permissions.entrySet()) {
permissionsBuilder.put(e.getKey(), ImmutableMap.copyOf(e.getValue()));
}
this.permissions = permissionsBuilder.build();
ImmutableMap.Builder<ImmutableContextSet, Map<String, String>> optionsBuilder = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, Map<String, String>> e : options.entrySet()) {
optionsBuilder.put(e.getKey(), ImmutableMap.copyOf(e.getValue()));
}
this.options = optionsBuilder.build();
ImmutableMap.Builder<ImmutableContextSet, List<SubjectReference>> parentsBuilder = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, List<SubjectReference>> e : parents.entrySet()) {
parentsBuilder.put(e.getKey(), ImmutableList.copyOf(e.getValue()));
}
this.parents = parentsBuilder.build();
}
public SubjectStorageModel(CalculatedSubjectData data) {
this(data.getPermissions(), data.getOptions(), data.getParentsAsList());
}
public SubjectStorageModel(JsonObject root) {
Preconditions.checkArgument(root.get("permissions").isJsonArray());
Preconditions.checkArgument(root.get("options").isJsonArray());
Preconditions.checkArgument(root.get("parents").isJsonArray());
JsonArray permissions = root.get("permissions").getAsJsonArray();
JsonArray options = root.get("options").getAsJsonArray();
JsonArray parents = root.get("parents").getAsJsonArray();
ImmutableMap.Builder<ImmutableContextSet, Map<String, Boolean>> permissionsBuilder = ImmutableMap.builder();
for (JsonElement e : permissions) {
if (!e.isJsonObject()) {
continue;
}
JsonObject section = e.getAsJsonObject();
if (!section.get("context").isJsonObject()) continue;
if (!section.get("data").isJsonObject()) continue;
JsonObject context = section.get("context").getAsJsonObject();
JsonObject data = section.get("data").getAsJsonObject();
ImmutableContextSet contextSet = contextsFromJson(context);
ImmutableMap.Builder<String, Boolean> perms = ImmutableMap.builder();
for (Map.Entry<String, JsonElement> perm : data.entrySet()) {
perms.put(perm.getKey(), perm.getValue().getAsBoolean());
}
permissionsBuilder.put(contextSet, perms.build());
}
this.permissions = permissionsBuilder.build();
ImmutableMap.Builder<ImmutableContextSet, Map<String, String>> optionsBuilder = ImmutableMap.builder();
for (JsonElement e : options) {
if (!e.isJsonObject()) {
continue;
}
JsonObject section = e.getAsJsonObject();
if (!section.get("context").isJsonObject()) continue;
if (!section.get("data").isJsonObject()) continue;
JsonObject context = section.get("context").getAsJsonObject();
JsonObject data = section.get("data").getAsJsonObject();
ImmutableContextSet contextSet = contextsFromJson(context);
ImmutableMap.Builder<String, String> opts = ImmutableMap.builder();
for (Map.Entry<String, JsonElement> opt : data.entrySet()) {
opts.put(opt.getKey(), opt.getValue().getAsString());
}
optionsBuilder.put(contextSet, opts.build());
}
this.options = optionsBuilder.build();
ImmutableMap.Builder<ImmutableContextSet, List<SubjectReference>> parentsBuilder = ImmutableMap.builder();
for (JsonElement e : parents) {
if (!e.isJsonObject()) {
continue;
}
JsonObject section = e.getAsJsonObject();
if (!section.get("context").isJsonObject()) continue;
if (!section.get("data").isJsonArray()) continue;
JsonObject context = section.get("context").getAsJsonObject();
JsonArray data = section.get("data").getAsJsonArray();
ImmutableContextSet contextSet = contextsFromJson(context);
ImmutableList.Builder<SubjectReference> pars = ImmutableList.builder();
for (JsonElement p : data) {
if (!p.isJsonObject()) {
continue;
}
JsonObject parent = p.getAsJsonObject();
String collection = parent.get("collection").getAsString();
String subject = parent.get("subject").getAsString();
pars.add(SubjectReference.of(collection, subject));
}
parentsBuilder.put(contextSet, pars.build());
}
this.parents = parentsBuilder.build();
}
public JsonObject toJson() {
JsonObject root = new JsonObject();
JsonArray permissions = new JsonArray();
for (Map.Entry<ImmutableContextSet, Map<String, Boolean>> e : this.permissions.entrySet()) {
if (e.getValue().isEmpty()) {
continue;
}
JsonObject section = new JsonObject();
section.add("context", contextsToJson(e.getKey()));
JsonObject data = new JsonObject();
for (Map.Entry<String, Boolean> ent : e.getValue().entrySet()) {
data.addProperty(ent.getKey(), ent.getValue());
}
section.add("data", data);
permissions.add(section);
}
root.add("permissions", permissions);
JsonArray options = new JsonArray();
for (Map.Entry<ImmutableContextSet, Map<String, String>> e : this.options.entrySet()) {
if (e.getValue().isEmpty()) {
continue;
}
JsonObject section = new JsonObject();
section.add("context", contextsToJson(e.getKey()));
JsonObject data = new JsonObject();
for (Map.Entry<String, String> ent : e.getValue().entrySet()) {
data.addProperty(ent.getKey(), ent.getValue());
}
section.add("data", data);
options.add(section);
}
root.add("options", options);
JsonArray parents = new JsonArray();
for (Map.Entry<ImmutableContextSet, List<SubjectReference>> e : this.parents.entrySet()) {
if (e.getValue().isEmpty()) {
continue;
}
JsonObject section = new JsonObject();
section.add("context", contextsToJson(e.getKey()));
JsonArray data = new JsonArray();
for (SubjectReference ref : e.getValue()) {
JsonObject parent = new JsonObject();
parent.addProperty("collection", ref.getCollection());
parent.addProperty("subject", ref.getCollection());
data.add(parent);
}
section.add("data", data);
options.add(section);
}
root.add("parents", parents);
return root;
}
public void applyToData(CalculatedSubjectData subjectData) {
subjectData.replacePermissions(permissions);
subjectData.replaceOptions(options);
subjectData.replaceParents(parents);
}
private static ImmutableContextSet contextsFromJson(JsonObject contexts) {
MutableContextSet ret = MutableContextSet.create();
for (Map.Entry<String, JsonElement> e : contexts.entrySet()) {
String key = e.getKey();
if (e.getValue().isJsonArray()) {
JsonArray values = e.getValue().getAsJsonArray();
for (JsonElement value : values) {
ret.add(key, value.getAsString());
}
} else {
ret.add(key, e.getValue().getAsString());
}
}
return ret.makeImmutable();
}
private static JsonObject contextsToJson(ContextSet contexts) {
JsonObject ret = new JsonObject();
for (Map.Entry<String, Collection<String>> e : contexts.toMultimap().asMap().entrySet()) {
String key = e.getKey();
List<String> values = new ArrayList<>(e.getValue());
if (values.size() == 1) {
ret.addProperty(key, values.get(0));
} else if (values.size() > 1) {
JsonArray arr = new JsonArray();
for (String s : values) {
arr.add(new JsonPrimitive(s));
}
ret.add(key, arr);
}
}
return ret;
}
}