Unify SchematicType loading

This commit is contained in:
2025-10-26 09:36:44 +01:00
parent ef81626e02
commit 167a6b6dc4
9 changed files with 53 additions and 98 deletions
-3
View File
@@ -22,7 +22,4 @@ plugins {
}
dependencies {
api(project(":CommonCore:SQL"))
implementation("org.yaml:snakeyaml:2.2")
}
@@ -1,828 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.data;
import de.steamwar.sql.SchematicType;
import lombok.ToString;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@ToString
public final class GameModeConfig<M, ST, W> {
public static final Function<String, String> ToString = Function.identity();
public static final Function<String, SchematicType> ToSchematicType = SchematicType::fromDB;
public static final Function<File, String> ToStaticWarGear = __ -> "WarGear";
public static final Function<File, String> ToInternalName = file -> file.getName().replace(".yml", "");
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
private static final Random random = new Random();
public final boolean loaded;
public final File configFile;
public final Server Server;
/**
* Submission deadline for schematics in 'dd.MM.yyyy HH:mm' format
* @implSpec {@code null} by default
*/
public final Date Deadline;
/**
* The questions that have to be answered to accept the schematic
*
* @implSpec Disables check schem type if missing
*/
public final List<String> CheckQuestions;
public final Times Times;
public final Arena Arena;
public final Schematic<M, ST> Schematic;
/**
* The name of the game mode presented to the players
*
* @implSpec {@code YMLWrapper.getDefaultGameName()} by default
*/
public final String GameName;
/**
* The months this game mode should be active and playable<br/>
* The empty List means all of them
*
* @implNote 1 is January - 12 is December
*/
public final List<Integer> ActiveMonths;
/**
* The prefix used for team chats
*
* @implSpec {@code +} by default
*/
public final String TeamChatPrefix;
public final Blue Blue;
public final Red Red;
/**
* The list of active win conditions
*/
public final List<W> WinConditions;
public final WinConditionParams<M> WinConditionParams;
public final Kits<M> Kits;
/**
* A list of integers containing the waiting time of this enter stage in the fight
*/
public final List<Integer> EnterStages;
public final Techhider<M> Techhider;
public GameModeConfig(File file, Function<String, M> materialMapper, Function<String, ST> schematicTypeMapper, Function<String, W> winconditionMapper, Function<File, String> defaultGameName) {
YMLWrapper<M, ST, W> loader = new YMLWrapper<>(file, materialMapper, schematicTypeMapper, winconditionMapper);
configFile = file;
loaded = loader.canLoad();
Server = new Server(loader.with("Server"));
String deadlineString = loader.getString("Deadline", null);
if (deadlineString != null) {
Date Deadline = null;
try {
Deadline = DATE_FORMAT.parse(deadlineString);
} catch (ParseException e) {
Deadline = null;
}
this.Deadline = Deadline;
} else {
Deadline = null;
}
CheckQuestions = loader.getStringList("CheckQuestions");
Times = new Times(loader.with("Times"));
// Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
Schematic = new Schematic<>(loader.with("Schematic"));
GameName = loader.getString("GameName", defaultGameName.apply(file));
ActiveMonths = loader.getIntList("ActiveMonths");
TeamChatPrefix = loader.getString("TeamChatPrefix", "+");
Blue = new Blue(loader.with("Blue"));
Red = new Red(loader.with("Red"));
WinConditions = Collections.unmodifiableList(loader.getStringList("WinConditions").stream().map(loader.winconditionMapper).collect(Collectors.toList()));
WinConditionParams = new WinConditionParams<>(loader.with("WinConditionParams"));
Kits = new Kits<>(loader.with("Kits"));
EnterStages = loader.getIntList("EnterStages");
Techhider = new Techhider<>(loader.with("Techhider"));
Arena = new Arena(loader.with("Arena"), Schematic.Size, EnterStages);
}
@ToString
public static final class Server {
public final boolean loaded;
/**
* Base server folder
*/
public final String Folder;
/**
* Server java archive
*/
public final String ServerJar;
/**
* Available arenas
*/
public final List<String> Maps;
/**
* Names to address the game mode in the chat interface
*/
public final List<String> ChatNames;
/**
* If the Server is a Spigot server
*
* @implSpec {@code false} by default
*/
public final boolean Spigot;
/**
* If the game mode should be marked as a historic game mode
*
* @implSpec {@code false} by default
*/
public final boolean Historic;
/**
* If ranked matches should be available for the game mode
*
* @implSpec {@code false} by default
*/
public final boolean Ranked;
private Server(YMLWrapper loader) {
loaded = loader.canLoad();
Folder = loader.getString("Folder", null);
ServerJar = loader.getString("ServerJar", null);
Maps = loader.getStringList("Maps");
ChatNames = loader.getStringList("ChatNames");
Spigot = loader.getBoolean("Spigot", false);
Historic = loader.getBoolean("Historic", false);
Ranked = loader.getBoolean("Ranked", false);
}
}
@ToString
public static final class Times {
public final boolean loaded;
/**
* Time in seconds the server stops after starting if nobody joins
*
* @implSpec {@code 300} by default
*/
public final int NoPlayersOnlineDuration;
/**
* Time in seconds the team leaders have to choose their schematic
*
* @implSpec {@code 120} by default
*/
public final int PreSchemPasteDuration;
/**
* Time in seconds for preparing
*
* @implSpec {@code 300} by default
*/
public final int SetupDuration;
/**
* Time in seconds the final countdown is long
*
* @implSpec {@code 30} by default
*/
public final int PreFightDuration;
/**
* Time in seconds to spectate the arena after the fight
*
* @implSpec {@code 30} by default
*/
public final int SpectatorDuration;
private Times(YMLWrapper loader) {
loaded = loader.canLoad();
NoPlayersOnlineDuration = loader.getInt("NoPlayersOnlineDuration", 300);
PreSchemPasteDuration = loader.getInt("PreSchemPasteDuration", 120);
SetupDuration = loader.getInt("SetupDuration", 300);
PreFightDuration = loader.getInt("PreFightDuration", 30);
SpectatorDuration = loader.getInt("SpectatorDuration", 60);
}
}
@ToString
public static final class Arena {
public final boolean loaded;
/**
* The amount of blocks the schematics should be pasted under the surface
*
* @implSpec {@code 0} by default
*/
public final int WaterDepth;
/**
* The outer border of the arena, measured in blocks around the schematic areas
*/
public final Schem2Border Schem2Border;
/**
* The offset the teams spawn relative to the center of their area
*/
public final SpawnOffset SpawnOffset;
/**
* The size of the team areas are expanded around the schematics
*
* @implSpec {@code 12} by default
*/
public final int BorderFromSchematic;
/**
* If ground walkable, teams can walk below the lower arena border during setup
*
* @implSpec {@code true} by default
*/
public final boolean GroundWalkable;
/**
* Disable snow and ice melting
*
* @implSpec {@code false} by default
*/
public final boolean DisableSnowMelt;
/**
* Allow leaving the arena area as spectator
*
* @implSpec {@code false} by default
*/
public final boolean Leaveable;
/**
* Allow missiles to fly to the enemy and not stop at the schem border.
*
* @implSpec {@code !EnterStages.isEmpty()} by default
*/
public final boolean AllowMissiles;
/**
* Denotes that there is no floor for this GameMode
*
* @implSpec {@code false} by default
*/
public final boolean NoFloor;
private Arena(YMLWrapper loader, Schematic.Size Size, List<Integer> EnterStages) {
loaded = loader.canLoad();
WaterDepth = loader.getInt("WaterDepth", 0);
Schem2Border = new Schem2Border(loader.with("Schem2Border"));
SpawnOffset = new SpawnOffset(loader.with("SpawnOffset"), Size);
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
GroundWalkable = loader.getBoolean("GroundWalkable", true);
DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false);
}
@ToString
public static final class Schem2Border {
public final boolean loaded;
/**
* @implSpec {@code 24} by default
*/
public final int x;
/**
* @implSpec {@code 24} by default
*/
public final int z;
private Schem2Border(YMLWrapper loader) {
loaded = loader.canLoad();
x = loader.getInt("x", 24);
z = loader.getInt("z", 24);
}
}
@ToString
public static final class SpawnOffset {
public final boolean loaded;
/**
* @implSpec {@code 0} by default
*/
public final double x;
/**
* @implSpec {@code Schematic.Size.y} by default
*/
public final double y;
/**
* @implSpec {@code 0} by default
*/
public final double z;
private SpawnOffset(YMLWrapper loader, Schematic.Size Size) {
loaded = loader.canLoad();
x = loader.getDouble("x", 0);
y = loader.getDouble("y", Size.y);
z = loader.getDouble("z", 0);
}
}
}
@ToString
public static final class Schematic<M, ST> {
public final boolean loaded;
/**
* The size of the schematics
*/
public final Size Size;
/**
* Used for GameModes with a technic area
*/
public final Inset Inset;
/**
* The schematic type that can be chosen in this arena
*
* @implSpec {@code Normal} by default
*/
public final ST Type;
/**
* The schematic types that are also allowed to be chosen in this arena
*/
public final List<ST> SubTypes;
/**
* Shortcut of the schematic type
*
* @implSpec {@code ""} by default
*/
public final String Shortcut;
/**
* Spigot (1.8) material for GUIs
*
* @implSpec {@code STONE_BUTTON} by default
*/
public final M Material;
/**
* Manual check of schematic necessary
*
* @implSpec {@code true} by default
*/
public final boolean ManualCheck;
/**
* If the schematics should be rotated during pasting
*
* @implSpec {@code true} by default
*/
public final boolean Rotate;
/**
* If the schematics should be pasted aligned to the borders instead of centered
*
* @implSpec {@code false} by default
*/
public final boolean PasteAligned;
/**
* If only public schematics are allowed
*
* @implSpec {@code false} by default
*/
public final boolean OnlyPublicSchematics;
/**
* If the public only force should be completely disabled
*
* @implSpec {@code false} by default
*/
public final boolean IgnorePublicOnly;
/**
* If obsidian and bedrock should be replaced during PRE_RUNNING
*
* @implSpec {@code false} by default
*/
public final boolean ReplaceObsidianBedrock;
/**
* If the replacement should happen with block updates
*
* @implSpec {@code false} by default
*/
public final boolean ReplaceWithBlockupdates;
/**
* If the schematic perparation arena mode is time limited
*
* @implSpec {@code false} by default
*/
public final boolean UnlimitedPrepare;
/**
* Maximal amount of blocks allowed in the schematic
*
* @implSpec {@code 0} by default
*/
public final int MaxBlocks;
/**
* Maximal amount of items per dispenser
*
* @implSpec {@code 128} by default
*/
public final int MaxDispenserItems;
/**
* Maximal blast resistance for the design blocks
*
* @implSpec {@code Double.MAX_VALUE} by default
*/
public final double MaxDesignBlastResistance;
/**
* List of limited material (combinations)<br/>
* List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format)
*/
public final Map<Set<M>, Integer> Limited;
private Schematic(YMLWrapper<M, ST, ?> loader) {
loaded = loader.canLoad();
Size = new Size(loader.with("Size"));
Inset = new Inset(loader.with("Inset"));
Type = loader.getSchematicType("Type", "Normal");
SubTypes = loader.getSchematicTypeList("SubTypes");
Shortcut = loader.getString("Shortcut", "");
Material = loader.getMaterial("Material", "STONE_BUTTON");
ManualCheck = loader.getBoolean("ManualCheck", true);
Rotate = loader.getBoolean("Rotate", true);
PasteAligned = loader.getBoolean("PasteAligned", false);
OnlyPublicSchematics = loader.getBoolean("OnlyPublicSchematics", false);
IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false);
ReplaceObsidianBedrock = loader.getBoolean("ReplaceObsidianBedrock", false);
ReplaceWithBlockupdates = loader.getBoolean("ReplaceWithBlockupdates", false);
UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false);
MaxBlocks = loader.getInt("MaxBlocks", 0);
MaxDispenserItems = loader.getInt("MaxDispenserItems", 128);
MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", Double.MAX_VALUE);
Map<Set<M>, Integer> Limited = new HashMap<>();
for (Map<?, ?> entry : loader.getMapList("Limited")) {
int amount = (Integer) entry.get("Amount");
Set<String> materials = new HashSet<>((List<String>) entry.get("Materials"));
if (amount == 0) {
materials.forEach(material -> {
Limited.put(Collections.singleton(loader.materialMapper.apply(material.toUpperCase())), 0);
});
} else {
Limited.put(Collections.unmodifiableSet(materials.stream().map(String::toUpperCase).map(loader.materialMapper).collect(Collectors.toSet())), amount);
}
}
this.Limited = Collections.unmodifiableMap(Limited);
}
@ToString
public static final class Size {
public final boolean loaded;
/**
* @implSpec {@code 0} by default
*/
public final int x;
/**
* @implSpec {@code 0} by default
*/
public final int y;
/**
* @implSpec {@code 0} by default
*/
public final int z;
private Size(YMLWrapper loader) {
loaded = loader.canLoad();
x = loader.getInt("x", 0);
y = loader.getInt("y", 0);
z = loader.getInt("z", 0);
}
}
@ToString
public static final class Inset {
public final boolean loaded;
/**
* @implSpec {@code 0} by default
*/
public final int x;
/**
* @implSpec {@code 0} by default
*/
public final int z;
/**
* @implSpec {@code 0} by default
*/
public final int top;
/**
* @implSpec {@code 0} by default
*/
public final int bottom;
private Inset(YMLWrapper loader) {
loaded = loader.canLoad();
x = loader.getInt("x", 0);
z = loader.getInt("z", 0);
top = loader.getInt("top", 0);
bottom = loader.getInt("bottom", 0);
}
}
}
@ToString
public static final class Blue {
public final boolean loaded;
/**
* @implSpec {@code Blau} by default
*/
public final String Name;
/**
* @implSpec {@code §3} by default
*/
public final String Prefix;
private Blue(YMLWrapper loader) {
loaded = loader.canLoad();
Name = loader.getString("Name", "Blau");
Prefix = loader.getString("Prefix", "§3");
}
}
@ToString
public static final class Red {
public final boolean loaded;
/**
* @implSpec {@code Rot} by default
*/
public final String Name;
/**
* @implSpec {@code §c} by default
*/
public final String Prefix;
private Red(YMLWrapper loader) {
loaded = loader.canLoad();
Name = loader.getString("Name", "Rot");
Prefix = loader.getString("Prefix", "§c");
}
}
@ToString
public static final class WinConditionParams<M> {
public final boolean loaded;
/**
* The time of any of the timeout win conditions in seconds
*
* @implSpec {@code 1200} by default
*/
public final int TimeoutTime;
/**
* The percentage when any of the percent win conditions limits or triggers a win
*
* @implSpec {@code 7.0} by default
*/
public final double PercentWin;
/**
* Does the percentage still change after the start of the enter phase
*
* @implSpec {@code true} by default
*/
public final boolean PercentEntern;
/**
* Is Blocks a whitelist (true) or blacklist (false)
*
* @implSpec {@code false} by default
*/
public final boolean BlocksWhitelist;
/**
* Special Blocks (Valid spigot material values) used by the percent win conditions
*/
public final List<M> Blocks;
/**
* Time for being declared TechKo without a shot given.
*
* @implSpec {@code 90} by default
*/
public final int TechKoTime;
private WinConditionParams(YMLWrapper<M, ?, ?> loader) {
loaded = loader.canLoad();
TimeoutTime = loader.getInt("TimeoutTime", 1200);
PercentWin = loader.getDouble("PercentWin", 7.0);
PercentEntern = loader.getBoolean("PercentEntern", true);
BlocksWhitelist = loader.getBoolean("BlocksWhitelist", false);
Blocks = loader.getMaterialList("Blocks");
TechKoTime = loader.getInt("TechKoTime", 90);
}
}
@ToString
public static final class Kits<M> {
public final boolean loaded;
/**
* The kit file for this configuration
*
* @implSpec {@code kits.yml} by default
*/
public final String File;
/**
* The default kit for team members
*
* @implSpec {@code default} by default
*/
public final String MemberDefault;
/**
* The default kit for team leaders
*
* @implSpec {@code default} by default
*/
public final String LeaderDefault;
/**
* If the personal kit system is active
*
* @implSpec {@code false} by default
*/
public final boolean PersonalKits;
/**
* Items (Valid spigot material values) that are not allowed in the personal kit
*/
public final List<M> ForbiddenItems;
private Kits(YMLWrapper<M, ?, ?> loader) {
loaded = loader.canLoad();
File = loader.getString("File", "kits.yml");
MemberDefault = loader.getString("MemberDefault", "default");
LeaderDefault = loader.getString("LeaderDefault", "default");
PersonalKits = loader.getBoolean("PersonalKits", false);
ForbiddenItems = loader.getMaterialList("ForbiddenItems");
}
}
@ToString
public static final class Techhider<M> {
public final boolean loaded;
/**
* Activates the tech hider
*
* @implSpec {@code false} by default
*/
public final boolean Active;
/**
* Which block the tech hider replaces to.
*
* @implSpec {@code end_stone} by default
*/
public final M ObfuscateWith;
/**
* A list of all hidden blocks. "water" results in the hiding of all waterlogged blocks as well.
*/
public final Set<M> HiddenBlocks;
/**
* The block entity contents that are hidden (here with minecraft:nametag)
*/
public final Set<String> HiddenBlockEntities;
private Techhider(YMLWrapper<M, ?, ?> loader) {
loaded = loader.canLoad();
Active = loader.getBoolean("Active", false);
ObfuscateWith = loader.getMaterial("ObfuscateWith", "end_stone");
HiddenBlocks = Collections.unmodifiableSet(new HashSet<>(loader.getMaterialList("HiddenBlocks")));
HiddenBlockEntities = Collections.unmodifiableSet(new HashSet<>(loader.getStringList("HiddenBlockEntities")));
}
}
public boolean isAfterDeadline() {
return Deadline != null && Deadline.before(Date.from(Instant.now()));
}
public String hasMap(String map) {
for (String m : Server.Maps) {
if (m.equalsIgnoreCase(map))
return m;
}
return null;
}
public String getRandomMap() {
return Server.Maps.get(random.nextInt(Server.Maps.size()));
}
public String convertToRealMapName(String map) {
for (String m : Server.Maps) {
if (m.equalsIgnoreCase(map))
return m;
}
return null;
}
public String getChatName() {
return Server.ChatNames.get(0);
}
public boolean isActive() {
if (Server.ChatNames.isEmpty()) return false;
if (ActiveMonths.isEmpty()) return true;
return ActiveMonths.contains(LocalDateTime.now().getMonth().getValue());
}
public String getSchemTypeOrInternalName() {
if (Schematic.loaded) {
ST type = Schematic.Type;
if (type instanceof SchematicType) {
return ((SchematicType) type).toDB();
} else if (type instanceof String) {
return (String) type;
}
}
return configFile.getName().replace(".yml", "");
}
}
@@ -1,164 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.data;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
final class YMLWrapper<M, ST, W> {
final Function<String, M> materialMapper;
final Function<String, ST> schematicTypeMapper;
final Function<String, W> winconditionMapper;
private final boolean canLoad;
private final Map<String, Object> document;
YMLWrapper(File file, Function<String, M> materialMapper, Function<String, ST> schematicTypeMapper, Function<String, W> winconditionMapper) {
this.materialMapper = materialMapper;
this.schematicTypeMapper = schematicTypeMapper;
this.winconditionMapper = winconditionMapper;
Yaml yaml = new Yaml();
Map<String, Object> document = Collections.emptyMap();
boolean canLoad = false;
if (file != null && file.exists() && file.isFile()) {
try {
document = yaml.load(new FileReader(file));
canLoad = true;
} catch (IOException e) {
// Ignore
}
}
this.document = document;
this.canLoad = canLoad;
}
private YMLWrapper(boolean canLoad, Map<String, Object> document, Function<String, M> materialMapper, Function<String, ST> schematicTypeMapper, Function<String, W> winconditionMapper) {
this.materialMapper = materialMapper;
this.schematicTypeMapper = schematicTypeMapper;
this.winconditionMapper = winconditionMapper;
this.canLoad = canLoad;
this.document = document;
}
public boolean canLoad() {
return canLoad;
}
public YMLWrapper<M, ST, W> with(String path) {
if (document.containsKey(path)) {
Object value = document.get(path);
if (value instanceof Map) {
return new YMLWrapper<>(true, (Map) value, materialMapper, schematicTypeMapper, winconditionMapper);
}
}
return new YMLWrapper<>(false, Collections.emptyMap(), materialMapper, schematicTypeMapper, winconditionMapper);
}
public <T> T get(String path, T defaultValue, Function<Object, T> mapper) {
Object value = this.document.get(path);
if (value == null) return defaultValue;
try {
return mapper.apply(value);
} catch (ClassCastException e) {
return defaultValue;
}
}
public String getString(String path, String defaultValue) {
return get(path, defaultValue, Objects::toString);
}
public int getInt(String path, int defaultValue) {
return get(path, defaultValue, Integer.class::cast);
}
public double getDouble(String path, double defaultValue) {
return get(path, defaultValue, Double.class::cast);
}
public boolean getBoolean(String path, boolean defaultValue) {
return get(path, defaultValue, Boolean.class::cast);
}
public ST getSchematicType(String path, String defaultValue) {
String schematicType = getString(path, defaultValue);
return schematicTypeMapper.apply(schematicType);
}
public M getMaterial(String path, String defaultValue) {
return materialMapper.apply(getString(path, defaultValue).toUpperCase());
}
public <T> List<T> get(String path, Function<Object, List<T>> mapper) {
Object value = this.document.get(path);
if (value == null) return Collections.emptyList();
try {
return Collections.unmodifiableList(mapper.apply(value));
} catch (ClassCastException e) {
return Collections.emptyList();
}
}
public List<String> getStringList(String path) {
return get(path, o -> (List<String>) o);
}
public List<Integer> getIntList(String path) {
return get(path, o -> (List<Integer>) o);
}
public List<ST> getSchematicTypeList(String path) {
List<String> list = getStringList(path);
if (list.isEmpty()) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(list.stream().map(schematicTypeMapper).collect(Collectors.toList()));
}
}
public List<M> getMaterialList(String path) {
List<String> list = getStringList(path);
if (list.isEmpty()) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(list.stream().map(String::toUpperCase).map(materialMapper).collect(Collectors.toList()));
}
}
public List<Map<?, ?>> getMapList(String path) {
Object value = this.document.get(path);
if (value instanceof List) {
return Collections.unmodifiableList((List<Map<?, ?>>) value);
} else {
return Collections.emptyList();
}
}
}