diff --git a/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index ac457ae94..c1f65fe48 100644 --- a/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -18,12 +18,16 @@ package com.volmit.iris.core.commands; +import com.google.gson.stream.JsonWriter; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.gui.NoiseExplorerGUI; import com.volmit.iris.core.gui.VisionGUI; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.project.IrisProject; +import com.volmit.iris.core.project.ProjectTrimmer; +import com.volmit.iris.core.service.CommandSVC; import com.volmit.iris.core.service.ConversionSVC; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; @@ -49,6 +53,7 @@ import com.volmit.iris.util.decree.DecreeExecutor; import com.volmit.iris.util.decree.DecreeOrigin; import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.decree.virtual.VirtualDecreeCommand; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.function.Function2; @@ -62,6 +67,7 @@ import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.Spiraler; import com.volmit.iris.util.noise.CNG; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.O; import com.volmit.iris.util.scheduling.PrecisionStopwatch; @@ -70,6 +76,7 @@ import com.volmit.iris.util.scheduling.jobs.JobCollection; import com.volmit.iris.util.scheduling.jobs.QueueJob; import com.volmit.iris.util.scheduling.jobs.SingleJob; import io.papermc.lib.PaperLib; +import org.apache.logging.log4j.core.util.JsonUtils; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FluidCollisionMode; @@ -83,17 +90,28 @@ import org.bukkit.util.Vector; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.attribute.FileTime; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; @Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true) public class CommandStudio implements DecreeExecutor { @@ -286,6 +304,128 @@ public class CommandStudio implements DecreeExecutor { new JobCollection("Cleaning", jobs).execute(sender()); } + @Decree(description = "Trim the pack of all unused files") + public void trim( + @Param(defaultValue = "overworld", description = "The pack to trim") + IrisDimension project + ) { + sender().sendMessage("Analyzing the " + project.getName() + " pack..."); + + ProjectTrimmer trimmer = new ProjectTrimmer(project); + long time = System.currentTimeMillis(); + trimmer.analyze(); + time = System.currentTimeMillis() - time; + sender().sendMessage("Done! Took " + time + "ms!"); + + Map, KList> result = trimmer.getResult(); + List> sorted = result.keySet().stream().filter(key -> result.get(key).size() > 0) + .sorted(Comparator.comparingInt(key -> result.get(key).size())).collect(Collectors.toList()); + + List realFiles = new ArrayList<>(); + + int total = result.values().stream().mapToInt(ArrayList::size).sum(); + if (sorted.size() > 0) { + sender().sendMessage("Found total of " + total + " unused files of " + sorted.size() + " different types"); + + for (Class clazz : sorted) { + try { + KList files = result.get(clazz); + + IrisRegistrant dummy = clazz.getConstructor().newInstance(); + String filesString1 = String.join(", ", files); + String filesString2 = filesString1.replaceAll(", ", "\n"); + String type = dummy.getTypeName(); + String chatString = "- " + files.size() + "x " + type + "s (" + filesString1; + chatString = chatString.substring(0, Math.min(chatString.length(), 56)) + "...)"; + + //Add the raw files to a list so we can delete them later + realFiles.addAll(files.stream().map(s -> dummy.getFolderName() + "/" + s + (clazz.equals(IrisObject.class) ? ".iob" : ".json")).collect(Collectors.toList())); + + sender().sendMessage("" + chatString + ""); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {} + } + + String password = UUID.randomUUID().toString().replaceAll("\\Q-\\E", ""); + final VolmitSender sender = sender(); + + Consumer confirm = (string) -> { + if (string.equalsIgnoreCase("delete") || string.equalsIgnoreCase("both")) { + sender.sendMessage(C.RED + "Deleting files..."); + + long time2 = System.currentTimeMillis(); + realFiles.forEach(s -> { + File file = new File(Iris.service(StudioSVC.class).getWorkspaceFolder(project.getLoadKey()), s); + if (!file.exists()) Iris.warn("Couldn't find file " + file.getPath() + " to delete"); + else IO.delete(file); + }); + + sender.sendMessage(C.GREEN + "Files deleted! Took " + (System.currentTimeMillis() - time2) + "ms!"); + if (!string.equalsIgnoreCase("both")) return; + } + + if (!(string.equalsIgnoreCase("file") || string.equalsIgnoreCase("both"))) { + sender.sendMessage(C.RED +"Unknown option \"" + string + "\". Writing to file anyway..."); + } + + File file = new File(Iris.service(StudioSVC.class).getWorkspaceFolder(project.getLoadKey()) + File.separator + "unused-files.txt"); + try { + List lines = new ArrayList<>(realFiles); + lines.add(0, "# Unused files in " + project.getLoadKey() + " project"); + lines.add(1, ""); + + if (file.exists()) file.delete(); + Files.write(file.toPath(), lines); + + String openFilePassword = UUID.randomUUID().toString().replaceAll("\\Q-\\E", ""); + Consumer openFile = (s) -> { + try { + Desktop.getDesktop().open(file); + } catch (IOException e) { + sender.sendMessage(C.RED + "Failed to open file: " + e.getLocalizedMessage()); + sender.sendMessage(C.RED + "See the console for details"); + e.printStackTrace(); + } + }; + + sender.sendMessage("" + C.DARK_GREEN + "Done! File written to " + file.getAbsolutePath() + ""); + + CompletableFuture onReturn2 = new CompletableFuture<>(); + onReturn2.thenAccept(openFile); + + Iris.service(CommandSVC.class).post(openFilePassword, onReturn2); + + } catch (IOException e) { + e.printStackTrace(); + } + }; + + CompletableFuture onReturn = new CompletableFuture<>(); + onReturn.thenAccept(confirm); + + if (sender().isPlayer()) { + sender().sendMessage(C.GREEN + "Click " + C.RED + "HERE" + C.GREEN + " to automatically delete these files."); + sender().sendMessage(C.GREEN + "Or instead, click " + C.BLUE + "HERE" + C.GREEN + " to create a file with a list of all the files."); + sender().sendMessage(C.GREEN + "Or, you can click " + C.YELLOW + "HERE" + C.GREEN + " to do both of these"); + + Iris.service(CommandSVC.class).post(password, onReturn); + } else { + sender().sendMessage(C.YELLOW + "Enter an option bellow to continue:"); + sender().sendMessage(C.GREEN + "DELETE - Delete all the files that are unused"); + sender().sendMessage(C.GREEN + "FILE - Write the list of all the unused files to a file"); + sender().sendMessage(C.GREEN + "BOTH - Do both of the above options"); + + Iris.service(CommandSVC.class).postConsole(onReturn); + } + + + + + } else { + sender().sendMessage(C.DARK_GREEN + "Analyzed entire project and found no unused files! Congrats!"); + } + + } + @Decree(description = "Get the version of a pack") public void version( @Param(defaultValue = "default", description = "The dimension get the version of", aliases = "dim", contextual = true) diff --git a/src/main/java/com/volmit/iris/core/project/ProjectTrimmer.java b/src/main/java/com/volmit/iris/core/project/ProjectTrimmer.java new file mode 100644 index 000000000..e95165bfc --- /dev/null +++ b/src/main/java/com/volmit/iris/core/project/ProjectTrimmer.java @@ -0,0 +1,533 @@ +package com.volmit.iris.core.project; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisCave; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.object.IrisEntity; +import com.volmit.iris.engine.object.IrisExpression; +import com.volmit.iris.engine.object.IrisGenerator; +import com.volmit.iris.engine.object.IrisJigsawPiece; +import com.volmit.iris.engine.object.IrisJigsawPool; +import com.volmit.iris.engine.object.IrisJigsawStructure; +import com.volmit.iris.engine.object.IrisLoot; +import com.volmit.iris.engine.object.IrisLootTable; +import com.volmit.iris.engine.object.IrisMarker; +import com.volmit.iris.engine.object.IrisObject; +import com.volmit.iris.engine.object.IrisRavine; +import com.volmit.iris.engine.object.IrisRegion; +import com.volmit.iris.engine.object.IrisScript; +import com.volmit.iris.engine.object.IrisSpawner; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.json.JSONArray; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.parallel.MultiBurst; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ProjectTrimmer { + + private IrisDimension dimension; + + private KList biomes; + private KList regions; + private KList caves; + private KList entities; + private KList objects; + private KList generators; + private KList expressions; + private KList loot; + private KList spawners; + private KList jigsawPieces; + private KList jigsawPools; + private KList jigsawStructures; + private KList scripts; + private KList markers; + private KList ravines; + + private IrisData data; + + private KList futureTasks = new KList<>(); + + public ProjectTrimmer(IrisDimension dimension) { + this.dimension = dimension; + + data = IrisData.get(Iris.service(StudioSVC.class).getWorkspaceFolder(dimension.getLoadKey())); + + biomes = new KList<>(data.getBiomeLoader().getPossibleKeys()); + regions = new KList<>(data.getRegionLoader().getPossibleKeys()); + caves = new KList<>(data.getCaveLoader().getPossibleKeys()); + entities = new KList<>(data.getEntityLoader().getPossibleKeys()); + objects = new KList<>(data.getObjectLoader().getPossibleKeys()); + generators = new KList<>(data.getGeneratorLoader().getPossibleKeys()); + expressions = new KList<>(data.getExpressionLoader().getPossibleKeys()); + loot = new KList<>(data.getLootLoader().getPossibleKeys()); + spawners = new KList<>(data.getSpawnerLoader().getPossibleKeys()); + jigsawPieces = new KList<>(data.getJigsawPieceLoader().getPossibleKeys()); + jigsawPools = new KList<>(data.getJigsawPoolLoader().getPossibleKeys()); + jigsawStructures = new KList<>(data.getJigsawStructureLoader().getPossibleKeys()); + scripts = new KList<>(data.getScriptLoader().getPossibleKeys()); + markers = new KList<>(data.getMarkerLoader().getPossibleKeys()); + ravines = new KList<>(data.getRavineLoader().getPossibleKeys()); + + if (objects.contains("null") || objects.contains(null)) + Iris.warn("Warning! Some objects are null! This means you have encountered the AtomicCache bug! If you delete the files, it may break something!"); + } + + /** + * Analyze the dimension pack. Do NOT run this on the main thread! + */ + public void analyze() { + + for (String dimension : data.getDimensionLoader().getPossibleKeys()) { + File file = data.getDimensionLoader().findFile(dimension); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("regions")) { + regions.removeAll(convertStringArray(json.getJSONArray("regions"))); + } + if (json.keySet().contains("stronghold")) { + jigsawStructures.remove(json.getString("stronghold")); + } + + analyzeCommonDRB(json); //Objects, structures + trimExpressions(json); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + KList usedRegions = new KList<>(Arrays.stream(data.getRegionLoader().getPossibleKeys()) + .filter(reg -> !this.regions.contains(reg)).collect(Collectors.toList())); + + //Regions + for (String region : usedRegions) { + futureTasks.add(() -> { + File file = data.getRegionLoader().findFile(region); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("landBiomes")) { + analyzeBiomeArray(json.getJSONArray("landBiomes")); + } + if (json.keySet().contains("seaBiomes")) { + analyzeBiomeArray(json.getJSONArray("seaBiomes")); + } + if (json.keySet().contains("shoreBiomes")) { + analyzeBiomeArray(json.getJSONArray("shoreBiomes")); + } + if (json.keySet().contains("caveBiomes")) { + analyzeBiomeArray(json.getJSONArray("caveBiomes")); + } + + analyzeCommonDRB(json); //Objects, structures + trimExpressions(json); + + } catch (IOException e) { + e.printStackTrace(); + } + + }); + } + + //Burst tasks for all biomes and regions + burstTasks(); + + + KList usedBiomes = new KList<>(Arrays.stream(data.getBiomeLoader().getPossibleKeys()) + .filter(biome -> !this.biomes.contains(biome)).collect(Collectors.toList())); + + //Biomes + for (String biome : usedBiomes) { + analyzeBiome(biome); + } + + burstTasks(); + + KList usedJigsaws = new KList<>(Arrays.stream(data.getJigsawStructureLoader().getPossibleKeys()) + .filter(jig -> !this.jigsawStructures.contains(jig)).collect(Collectors.toList())); + + + + for (String jigsaw : usedJigsaws) { + futureTasks.add(() -> { + File file = data.getJigsawStructureLoader().findFile(jigsaw); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("pieces")) { + JSONArray pieces = json.getJSONArray("pieces"); + for (int i = 0; i < pieces.length(); i++) { + String pieceString = pieces.getString(i); + File pieceFile = data.getJigsawPieceLoader().findFile(pieceString); + this.jigsawPieces.remove(pieceString); + analyzeJigsawObject(new JSONObject(IO.readAll(pieceFile))); + } + + } + } catch (IOException e) {} + }); + } + + burstTasks(); + + KList usedEntities = new KList<>(Arrays.stream(data.getEntityLoader().getPossibleKeys()) + .filter(jig -> !this.entities.contains(jig)).collect(Collectors.toList())); + + for (String entity : usedEntities) { + futureTasks.add(() -> { + File file = data.getEntityLoader().findFile(entity); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("postSpawnScripts")) { + scripts.removeAll(convertStringArray(json.getJSONArray("postSpawnScripts"))); + } + if (json.keySet().contains("spawnerScripts")) { + scripts.remove(json.getString("spawnerScripts")); + } + } catch (IOException e) {} + }); + } + + burstTasks(); + + } + + /** + * Get all the unused files in the project after calling {@link #analyze()} + * @return A map of all the unused files by type + */ + public Map, KList> getResult() { + Map, KList> map = new HashMap<>(); + map.put(IrisBiome.class, biomes); + map.put(IrisRegion.class, regions); + map.put(IrisCave.class, caves); + map.put(IrisRavine.class, ravines); + map.put(IrisObject.class, objects); + map.put(IrisEntity.class, entities); + map.put(IrisGenerator.class, generators); + map.put(IrisSpawner.class, spawners); + map.put(IrisLootTable.class, loot); + map.put(IrisExpression.class, expressions); + map.put(IrisScript.class, scripts); + map.put(IrisJigsawPiece.class, jigsawPieces); + map.put(IrisJigsawPool.class, jigsawPools); + map.put(IrisJigsawStructure.class, jigsawStructures); + map.put(IrisMarker.class, markers); + + return map; + } + + /** + * Do all queued tasks in a burst + */ + private void burstTasks() { + while (futureTasks.size() > 0) { + KList clonedTasks = new KList<>(futureTasks); + futureTasks.clear(); + + MultiBurst burster = new MultiBurst(); + burster.burst(clonedTasks); //The tasks themselves can add more tasks to futureTasks + } + } + + + + /** + * Analyze common things within dimensions, regions and objects + * @param object The parent json object + */ + private void analyzeCommonDRB(JSONObject object) { + //Jigsaws + if (object.keySet().contains("jigsawStructures")) { + JSONArray jigsaws = object.getJSONArray("jigsawStructures"); + for (int i = 0; i < jigsaws.length(); i++) { + JSONObject jigobject = jigsaws.getJSONObject(i); + jigsawStructures.remove(jigobject.getString("structure")); + } + } + + //Objects + if (object.keySet().contains("objects")) { + JSONArray objectsArray = object.getJSONArray("objects"); + for (int i = 0; i < objectsArray.length(); i++) { + JSONObject innerObject = objectsArray.getJSONObject(i); //The actual object within the objects + + analyzeObject(innerObject); + } + } + + //Loot + if (object.keySet().contains("loot")) { + JSONObject lootObject = object.getJSONObject("loot"); + this.loot.removeAll(convertStringArray(lootObject.getJSONArray("tables"))); + } + + //Spawners + if (object.keySet().contains("entitySpawners")) { + JSONArray entitySpawners = object.getJSONArray("entitySpawners"); + analyzeSpawnerArray(entitySpawners); + } + + //Caves and stuff + if (object.keySet().contains("carvings")) { + JSONObject carvings = object.getJSONObject("carvings"); + + if (carvings.keySet().contains("caves")) { + JSONArray array = carvings.getJSONArray("caves"); + for (int i = 0; i < array.length(); i++) { + JSONObject cave = array.getJSONObject(i); + this.caves.remove(cave.getString("cave")); + } + } + if (carvings.keySet().contains("ravines")) { + JSONArray array = carvings.getJSONArray("ravines"); + for (int i = 0; i < array.length(); i++) { + JSONObject cave = array.getJSONObject(i); + this.ravines.remove(cave.getString("ravine")); + } + } + } + } + + private void analyzeObject(JSONObject object) { + JSONArray placeArray = object.getJSONArray("place"); + objects.removeAll(convertStringArray(placeArray)); + + if (object.keySet().contains("loot")) { + JSONArray loot = object.getJSONArray("loot"); + for (int j = 0; j < loot.length(); j++) { + JSONObject lootObject = loot.getJSONObject(j); + this.loot.remove(lootObject.getString("name")); + } + } + + if (object.keySet().contains("markers")) { + JSONArray markers = object.getJSONArray("markers"); + for (int j = 0; j < markers.length(); j++) { + JSONObject marker = markers.getJSONObject(j); + String markerName = marker.getString("marker"); + analyzeMarker(markerName); + } + } + } + + private void analyzeJigsawObject(JSONObject object) { + objects.remove(object.getString("object")); + + JSONObject placement = object.getJSONObject("placementOptions"); + if (placement.keySet().contains("loot")) { + JSONArray loot = placement.getJSONArray("loot"); + for (int j = 0; j < loot.length(); j++) { + JSONObject lootObject = loot.getJSONObject(j); + this.loot.remove(lootObject.getString("name")); + } + } + if (placement.keySet().contains("markers")) { + JSONArray markers = placement.getJSONArray("markers"); + for (int j = 0; j < markers.length(); j++) { + JSONObject marker = markers.getJSONObject(j); + String markerName = marker.getString("marker"); + analyzeMarker(markerName); + } + } + + if (object.keySet().contains("connectors")) { + JSONArray connectors = object.getJSONArray("connectors"); + for (int j = 0; j < connectors.length(); j++) { + JSONObject connector = connectors.getJSONObject(j); + + if (connector.keySet().contains("pools")) { + JSONArray pools = connector.getJSONArray("pools"); + analyzePoolArray(pools); + } + } + + } + } + + private void analyzeMarker(String name) { + if (markers.contains(name)) { + markers.remove(name); + futureTasks.add(() -> { + File file = data.getMarkerLoader().findFile(name); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("spawners")) { + analyzeSpawnerArray(json.getJSONArray("spawners")); + } + } catch (IOException e) { + + } + }); + } + } + + private void analyzeSpawnerArray(JSONArray spawners) { + for (int i = 0; i < spawners.length(); i++) { + String spawner = spawners.getString(i); + + if (this.spawners.contains(spawner)) { + this.spawners.remove(spawner); + + futureTasks.add(() -> { + File file = data.getSpawnerLoader().findFile(spawner); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("spawns")) { + JSONArray spawnArray = json.getJSONArray("spawns"); + for (int j = 0; j < spawnArray.length(); j++) { + JSONObject spawnObject = spawnArray.getJSONObject(j); + entities.remove(spawnObject.getString("entity")); + } + } + if (json.keySet().contains("initialSpawns")) { + JSONArray spawnArray = json.getJSONArray("initialSpawns"); + for (int j = 0; j < spawnArray.length(); j++) { + JSONObject spawnObject = spawnArray.getJSONObject(j); + entities.remove(spawnObject.getString("entity")); + } + } + + } catch (IOException e) { } + }); + } + } + } + + private void analyzeBiome(String biome) { + if (biomes.contains(biome)) { + biomes.remove(biome); + + futureTasks.add(() -> { + File file = data.getBiomeLoader().findFile(biome); + + try { + JSONObject json = new JSONObject(IO.readAll(file)); + if (json.keySet().contains("children")) { + analyzeBiomeArray(json.getJSONArray("children")); + } + if (json.keySet().contains("generators")) { + JSONArray ogenerators = json.getJSONArray("generators"); + for (int j = 0; j < ogenerators.length(); j++) { + JSONObject genobject = ogenerators.getJSONObject(j); + generators.remove(genobject.getString("generator")); + } + } + analyzeCommonDRB(json); //Objects, structures + trimExpressions(json); + + } catch (IOException e) { + e.printStackTrace(); + } + + }); + } + } + + private void analyzeBiomeArray(JSONArray array) { + for (int i = 0; i < array.length(); i++) { + String biome = array.getString(i); + + analyzeBiome(biome); + } + } + + private void analyzePoolArray(JSONArray array) { + for (int i = 0; i < array.length(); i++) { + String pool = array.getString(i); + + if (this.jigsawPools.contains(pool)) { + this.jigsawPools.remove(pool); + + futureTasks.add(() -> { + File file = data.getJigsawPoolLoader().findFile(pool); + try { + JSONObject json = new JSONObject(IO.readAll(file)); + + if (json.keySet().contains("pieces")) { + JSONArray pieces = json.getJSONArray("pieces"); + + for (String piece : convertStringArray(pieces)) { + if (this.jigsawPieces.contains(piece)) { + futureTasks.add(() -> { + + File pieceFile = data.getJigsawPoolLoader().findFile(pool); + try { + JSONObject pieceJSON = new JSONObject(IO.readAll(pieceFile)); + analyzeJigsawObject(pieceJSON); + } catch (IOException e) { + + } + }); + } + } + } + } catch (IOException e) { } + }); + } + + } + } + + /** + * Converts a JSON string array to a java list + * @param array The JSON string array + * @return The list + */ + private List convertStringArray(JSONArray array) { + List newList = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + String s = array.getString(i); + if (s != null) newList.add(s); + } + return newList; + } + + /** + * Scans for styles with expressions in them and removes all used expressions + * @param object The parent json + */ + private void trimExpressions(JSONObject object) { + for (String key : object.keySet()) { + Object o = object.get(key); + + if (key.equalsIgnoreCase("style")) { + if (o instanceof JSONObject && ((JSONObject) o).keySet().contains("expression")) { + this.expressions.remove(((JSONObject) o).getString("expression")); + } + } + + if (o instanceof JSONObject) { + trimExpressions((JSONObject)o); + } else if (o instanceof JSONArray) { + JSONArray array = (JSONArray)o; + for (int i = 0; i < array.length(); i++) { + Object o2 = array.get(i); + + if (o2 instanceof JSONObject) { + trimExpressions((JSONObject) o2); + } + } + } + } + } +}