diff --git a/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java b/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java index 15cae9d3..e42616e5 100644 --- a/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java +++ b/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java @@ -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 toMultimap() { - return map; + return ImmutableSetMultimap.copyOf(map); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java index 78c0e037..5ef74920 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java @@ -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); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java index 520ec904..ebe1b46f 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java @@ -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); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java index ba3eeac2..67c7882e 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java @@ -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> map) { + public void replacePermissions(Map> map) { permissions.clear(); - for (Map.Entry> e : map.entrySet()) { - permissions.put(e.getKey().makeImmutable(), new ConcurrentHashMap<>(e.getValue())); + for (Map.Entry> e : map.entrySet()) { + permissions.put(e.getKey(), new ConcurrentHashMap<>(e.getValue())); } permissionCache.invalidateAll(); service.invalidatePermissionCaches(); } - public void replaceParents(Map> map) { + public void replaceParents(Map> map) { parents.clear(); - for (Map.Entry> e : map.entrySet()) { + for (Map.Entry> e : map.entrySet()) { Set set = ConcurrentHashMap.newKeySet(); set.addAll(e.getValue()); - parents.put(e.getKey().makeImmutable(), set); + parents.put(e.getKey(), set); } service.invalidateParentCaches(); } - public void replaceOptions(Map> map) { + public void replaceOptions(Map> map) { options.clear(); - for (Map.Entry> e : map.entrySet()) { - options.put(e.getKey().makeImmutable(), new ConcurrentHashMap<>(e.getValue())); + for (Map.Entry> 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> getParentsAsList() { + ImmutableMap.Builder> map = ImmutableMap.builder(); + for (Map.Entry> e : parents.entrySet()) { + map.put(e.getKey().makeImmutable(), ImmutableList.copyOf(e.getValue())); + } + return map.build(); + } + @Override public Set getParents(ContextSet contexts) { return ImmutableSet.copyOf(parents.getOrDefault(contexts, ImmutableSet.of())); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/legacystorage/LegacyDataMigrator.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/legacystorage/LegacyDataMigrator.java new file mode 100644 index 00000000..88c0b701 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/legacystorage/LegacyDataMigrator.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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(); + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/legacystorage/SubjectDataHolder.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/legacystorage/SubjectDataHolder.java new file mode 100644 index 00000000..1ba2cc7f --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/legacystorage/SubjectDataHolder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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> permissions; + private Map, Map> options; + private Map, List> 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()) + )) + ); + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java index 6a68f96c..3190ec6b 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java @@ -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 holders = service.getStorage().loadAllFromFile(identifier); - for (Map.Entry e : holders.entrySet()) { + Map holders = service.getStorage().loadAllFromFile(identifier); + for (Map.Entry e : holders.entrySet()) { PersistedSubject subject = get(e.getKey()); subject.loadData(e.getValue()); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java index fb5609d6..0a3d1f05 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java @@ -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); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java deleted file mode 100644 index ce72e9aa..00000000 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2016 Lucko (Luck) - * - * 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> permissions; - private final Map, Map> options; - private final Map, List> parents; - - public SubjectDataHolder(Map> options, Map> permissions, Map> parents) { - this.options = new HashMap<>(); - for (Map.Entry> e : options.entrySet()) { - if (!e.getValue().isEmpty()) { - this.options.put(e.getKey().toMap(), new HashMap<>(e.getValue())); - } - } - - this.permissions = new HashMap<>(); - for (Map.Entry> e : permissions.entrySet()) { - if (!e.getValue().isEmpty()) { - this.permissions.put(e.getKey().toMap(), new HashMap<>(e.getValue())); - } - } - - this.parents = new HashMap<>(); - for (Map.Entry> 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()) - )) - ); - } -} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/storage/SubjectStorage.java similarity index 62% rename from sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java rename to sponge/src/main/java/me/lucko/luckperms/sponge/service/storage/SubjectStorage.java index 0d9ce33c..02dc6cd2 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/storage/SubjectStorage.java @@ -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 loadAllFromFile(String collectionName) { + public Map 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 holders = new HashMap<>(); + Map holders = new HashMap<>(); for (String name : fileNames) { File subject = new File(collection, name); try { - Map.Entry s = loadFromFile(subject); + Map.Entry s = loadFromFile(subject); if (s != null) { holders.put(s.getKey(), s.getValue()); } @@ -117,7 +130,7 @@ public class SubjectStorage { return holders; } - public Map.Entry loadFromFile(String collectionName, String subjectName) throws IOException { + public Map.Entry 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 loadFromFile(File file) throws IOException { + public Map.Entry 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); + } } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/storage/SubjectStorageModel.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/storage/SubjectStorageModel.java new file mode 100644 index 00000000..2e133a3c --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/storage/SubjectStorageModel.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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> permissions; + private final Map> options; + private final Map> parents; + + public SubjectStorageModel(Map> permissions, Map> options, Map> parents) { + ImmutableMap.Builder> permissionsBuilder = ImmutableMap.builder(); + for (Map.Entry> e : permissions.entrySet()) { + permissionsBuilder.put(e.getKey(), ImmutableMap.copyOf(e.getValue())); + } + this.permissions = permissionsBuilder.build(); + + ImmutableMap.Builder> optionsBuilder = ImmutableMap.builder(); + for (Map.Entry> e : options.entrySet()) { + optionsBuilder.put(e.getKey(), ImmutableMap.copyOf(e.getValue())); + } + this.options = optionsBuilder.build(); + + ImmutableMap.Builder> parentsBuilder = ImmutableMap.builder(); + for (Map.Entry> 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> 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 perms = ImmutableMap.builder(); + for (Map.Entry perm : data.entrySet()) { + perms.put(perm.getKey(), perm.getValue().getAsBoolean()); + } + + permissionsBuilder.put(contextSet, perms.build()); + } + this.permissions = permissionsBuilder.build(); + + ImmutableMap.Builder> 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 opts = ImmutableMap.builder(); + for (Map.Entry opt : data.entrySet()) { + opts.put(opt.getKey(), opt.getValue().getAsString()); + } + + optionsBuilder.put(contextSet, opts.build()); + } + this.options = optionsBuilder.build(); + + ImmutableMap.Builder> 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 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> 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 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> 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 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> 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 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> e : contexts.toMultimap().asMap().entrySet()) { + String key = e.getKey(); + List 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; + } +}