Made YamlStore fit its interfaces so that there are no more compile errors.

It is, however, not yet ready, because it doesn't yet support custom flags or load in the old domain data.
This commit is contained in:
sk89q 2012-11-16 16:05:51 -08:00
parent 58fa6d1c0b
commit fa11dd6d62

View File

@ -21,120 +21,170 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.FileNotFoundException; import java.util.Collection;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Map.Entry;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.sk89q.util.yaml.YAMLFormat; import org.apache.commons.lang.Validate;
import com.sk89q.util.yaml.YAMLNode; import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import com.sk89q.util.yaml.YAMLProcessor;
import com.sk89q.worldedit.BlockVector; import com.sk89q.rebar.config.ConfigurationException;
import com.sk89q.rebar.config.ConfigurationNode;
import com.sk89q.rebar.config.YamlConfiguration;
import com.sk89q.rebar.config.YamlConfigurationFile;
import com.sk89q.rebar.config.YamlStyle;
import com.sk89q.rebar.config.types.BlockVector2dLoaderBuilder;
import com.sk89q.rebar.config.types.ListBuilder;
import com.sk89q.rebar.config.types.VectorLoaderBuilder;
import com.sk89q.rebar.util.LoggerUtils;
import com.sk89q.worldedit.BlockVector2D; import com.sk89q.worldedit.BlockVector2D;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldguard.domains.DefaultDomain;
import com.sk89q.worldguard.region.Region; import com.sk89q.worldguard.region.Region;
import com.sk89q.worldguard.region.Region.CircularInheritanceException;
import com.sk89q.worldguard.region.flags.DefaultFlag; import com.sk89q.worldguard.region.flags.DefaultFlag;
import com.sk89q.worldguard.region.flags.Flag; import com.sk89q.worldguard.region.flags.Flag;
import com.sk89q.worldguard.region.indices.RegionIndex;
import com.sk89q.worldguard.region.indices.RegionIndexFactory;
import com.sk89q.worldguard.region.shapes.Cuboid; import com.sk89q.worldguard.region.shapes.Cuboid;
import com.sk89q.worldguard.region.shapes.Everywhere;
import com.sk89q.worldguard.region.shapes.ExtrudedPolygon; import com.sk89q.worldguard.region.shapes.ExtrudedPolygon;
import com.sk89q.worldguard.region.shapes.GlobalProtectedRegion; import com.sk89q.worldguard.region.shapes.IndexableShape;
public class YamlStore extends AbstractProtectionDatabase { /**
* A YAML-based region store that uses {@link YamlConfiguration} in order to store
private YAMLProcessor config; * region data.
private Map<String, Region> regions; * <p>
private final Logger logger; * The main advantage of this format is that output files can easily be edited by
* hand, but the disadvantage is that it's not fast as a format that writes more
public YamlStore(File file, Logger logger) throws ProtectionDatabaseException, FileNotFoundException { * native data types. In addition, while YAML is not nearly verbose as XML, being a
this.logger = logger; * text-based format making heavy use of indentation, the size of data files written
if (!file.exists()) { // shouldn't be necessary, but check anyways * by this format can be quite large.
try { * <p>
file.createNewFile(); * To mitigate some of the impacts of this format, this store uses only an indentation
} catch (IOException e) { * size of two spaces to reduce disk space usage.
throw new FileNotFoundException(file.getAbsolutePath()); */
} public class YamlStore implements RegionStore {
}
config = new YAMLProcessor(file, false, YAMLFormat.COMPACT); private static final Logger defaultLogger = LoggerUtils.getLogger(YamlStore.class);
private static final YamlStyle style = new YamlStyle(FlowStyle.FLOW, 2);
private static final VectorLoaderBuilder vectorLB = new VectorLoaderBuilder();
private static final BlockVector2dLoaderBuilder blockVec2dLB = new BlockVector2dLoaderBuilder();
private Logger logger = defaultLogger;
private final File file;
/**
* Create a new YAML-based store that uses the provided file for storing data.
* <p>
* The file does not yet have to exist, but it does need to be readable and
* writable in order for regions to be loaded or saved.
*
* @param file file to store data in
*/
public YamlStore(File file) {
this.file = file;
} }
public void load() throws ProtectionDatabaseException { /**
* Get the logger assigned to this store.
* <p>
* Messages will be relayed through the logger set on this store if possible.
*
* @return logger, or null
*/
public Logger getLogger() {
return logger;
}
/**
* Set the logger assigned to this store.
* <p>
* Messages will be relayed through the logger set on this store if possible.
*
* @param logger or null
*/
public void setLogger(Logger logger) {
this.logger = logger;
}
@Override
public RegionIndex load(RegionIndexFactory factory) throws IOException {
YamlConfiguration config = new YamlConfigurationFile(file);
try { try {
config.load(); config.load(); // Load data (may raise an exception)
} catch (IOException e) { } catch (ConfigurationException e) {
throw new ProtectionDatabaseException(e); throw new IOException("Fatal syntax or other error in YAML-formatted data", e);
}
Map<String, YAMLNode> regionData = config.getNodes("regions");
// No regions are even configured
if (regionData == null) {
this.regions = new HashMap<String, Region>();
return;
} }
Map<String,Region> regions = // Create an index
new HashMap<String,Region>(); RegionIndex index = factory.newIndex();
Map<Region,String> parentSets =
new LinkedHashMap<Region, String>(); // Store a list of parent relationships that we have to set later on
Map<Region, String> parentSets = new LinkedHashMap<Region, String>();
for (Map.Entry<String, YAMLNode> entry : regionData.entrySet()) {
String id = entry.getKey().toLowerCase().replace(".", ""); for (Entry<String, ConfigurationNode> entry : config.getNodes("regions").entrySet()) {
YAMLNode node = entry.getValue(); ConfigurationNode node = entry.getValue();
String type = node.getString("type");
Region region;
try { try {
if (type == null) { IndexableShape shape;
logger.warning("Undefined region type for region '" + id + '"'); String id = node.getString("id");
continue; String type = node.getString("type");
} else if (type.equals("cuboid")) {
Vector pt1 = checkNonNull(node.getVector("min")); Validate.notNull(id, "Missing an 'id' parameter for: " + node);
Vector pt2 = checkNonNull(node.getVector("max")); Validate.notNull(type, "Missing a 'type' parameter for: " + node);
BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector();
BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); // Axis-aligned cuboid type
region = new Cuboid(id, min, max); if (type.equals("cuboid")) {
Vector pt1 = config.getOf("min", vectorLB);
Vector pt2 = config.getOf("max", vectorLB);
Validate.notNull(pt1, "Missing a 'min' parameter for: " + node);
Validate.notNull(pt2, "Missing a 'max' parameter for: " + node);
shape = new Cuboid(pt1, pt2);
// Extruded polygon type
} else if (type.equals("poly2d")) { } else if (type.equals("poly2d")) {
Integer minY = checkNonNull(node.getInt("min-y")); Integer minY = node.getInt("min-y");
Integer maxY = checkNonNull(node.getInt("max-y")); Integer maxY = node.getInt("max-y");
List<BlockVector2D> points = node.getBlockVector2dList("points", null); Validate.notNull(minY, "Missing a 'min-y' parameter for: " + node);
region = new ExtrudedPolygon(id, points, minY, maxY); Validate.notNull(maxY, "Missing a 'max-y' parameter for: " + node);
List<BlockVector2D> points = node.listOf("points", blockVec2dLB);
// Note: Invalid points are discarded!
shape = new ExtrudedPolygon(points, minY, maxY);
// "Everywhere" type
} else if (type.equals("global")) { } else if (type.equals("global")) {
region = new GlobalProtectedRegion(id); shape = new Everywhere();
// ???
} else { } else {
logger.warning("Unknown region type for region '" + id + '"'); throw new IllegalArgumentException("Don't know what type of shape '" +
continue; type + "' is! In: " + node);
} }
Integer priority = checkNonNull(node.getInt("priority")); Region region = new Region(id, shape);
region.setPriority(priority); region.setPriority(node.getInt("priority", 0));
setFlags(region, node.getNode("flags")); loadFlags(region, node.getNode("flags"));
region.setOwners(parseDomain(node.getNode("owners")));
region.setMembers(parseDomain(node.getNode("members"))); // Remember the parent so that it can be linked lateron
regions.put(id, region);
String parentId = node.getString("parent"); String parentId = node.getString("parent");
if (parentId != null) { if (parentId != null) {
parentSets.put(region, parentId); parentSets.put(region, parentId);
} }
} catch (NullPointerException e) { } catch (IllegalArgumentException e) {
logger.warning("Missing data for region '" + id + '"'); logger.warning(e.getMessage());
} }
} }
// Relink parents // Re-link parents
for (Map.Entry<Region, String> entry : parentSets.entrySet()) { for (Map.Entry<Region, String> entry : parentSets.entrySet()) {
Region parent = regions.get(entry.getValue()); Region parent = index.get(entry.getValue());
if (parent != null) { if (parent != null) {
try { try {
entry.getKey().setParent(parent); entry.getKey().setParent(parent);
} catch (CircularInheritanceException e) { } catch (IllegalArgumentException e) {
logger.warning("Circular inheritance detect with '" logger.warning("Circular inheritance detect with '"
+ entry.getValue() + "' detected as a parent"); + entry.getValue() + "' detected as a parent");
} }
@ -142,174 +192,135 @@ public void load() throws ProtectionDatabaseException {
logger.warning("Unknown region parent: " + entry.getValue()); logger.warning("Unknown region parent: " + entry.getValue());
} }
} }
this.regions = regions; return index;
}
private <V> V checkNonNull(V val) throws NullPointerException {
if (val == null) {
throw new NullPointerException();
}
return val;
}
private void setFlags(Region region, YAMLNode flagsData) {
if (flagsData == null) {
return;
}
// @TODO: Make this better
for (Flag<?> flag : DefaultFlag.getFlags()) {
Object o = flagsData.getProperty(flag.getName());
if (o != null) {
setFlag(region, flag, o);
}
if (flag.getRegionGroupFlag() != null) {
Object o2 = flagsData.getProperty(flag.getRegionGroupFlag().getName());
if (o2 != null) {
setFlag(region, flag.getRegionGroupFlag(), o2);
}
}
}
}
private <T> void setFlag(Region region, Flag<T> flag, Object rawValue) {
T val = flag.unmarshal(rawValue);
if (val == null) {
logger.warning("Failed to parse flag '" + flag.getName()
+ "' with value '" + rawValue.toString() + "'");
return;
}
region.setFlag(flag, val);
}
private DefaultDomain parseDomain(YAMLNode node) {
if (node == null) {
return new DefaultDomain();
}
DefaultDomain domain = new DefaultDomain();
for (String name : node.getStringList("players", null)) {
domain.addPlayer(name);
}
for (String name : node.getStringList("groups", null)) {
domain.addGroup(name);
}
return domain;
} }
public void save() throws ProtectionDatabaseException { @Override
config.clear(); public void save(Collection<Region> regions) throws IOException {
YamlConfiguration config = new YamlConfigurationFile(file, style);
for (Map.Entry<String, Region> entry : regions.entrySet()) {
Region region = entry.getValue(); for (Region region : regions) {
YAMLNode node = config.addNode("regions." + entry.getKey()); ConfigurationNode node = config.setNode("regions").setNode(region.getId());
IndexableShape shape = region.getShape();
if (region instanceof Cuboid) {
Cuboid cuboid = (Cuboid) region; if (shape instanceof Cuboid) {
node.setProperty("type", "cuboid"); Cuboid cuboid = (Cuboid) shape;
node.setProperty("min", cuboid.getAABBMin()); node.set("type", "cuboid");
node.setProperty("max", cuboid.getAABBMax()); node.set("min", cuboid.getAABBMin(), blockVec2dLB);
} else if (region instanceof ExtrudedPolygon) { node.set("max", cuboid.getAABBMax(), blockVec2dLB);
ExtrudedPolygon poly = (ExtrudedPolygon) region; } else if (shape instanceof ExtrudedPolygon) {
node.setProperty("type", "poly2d"); ExtrudedPolygon poly = (ExtrudedPolygon) shape;
node.setProperty("min-y", poly.getAABBMin().getBlockY()); node.set("type", "poly2d");
node.setProperty("max-y", poly.getAABBMax().getBlockY()); node.set("min-y", poly.getAABBMin().getBlockY());
node.set("max-y", poly.getAABBMax().getBlockY());
List<Map<String, Object>> points = new ArrayList<Map<String,Object>>(); node.set("points", poly.getProjectedVerts(),
for (BlockVector2D point : poly.getPoints()) { new ListBuilder<BlockVector2D>(blockVec2dLB));
Map<String, Object> data = new HashMap<String, Object>(); } else if (shape instanceof Everywhere) {
data.put("x", point.getBlockX()); node.set("type", "global");
data.put("z", point.getBlockZ());
points.add(data);
}
node.setProperty("points", points);
} else if (region instanceof GlobalProtectedRegion) {
node.setProperty("type", "global");
} else { } else {
node.setProperty("type", region.getClass().getCanonicalName()); // This means that it's not supported!
node.set("type", region.getClass().getCanonicalName());
} }
node.setProperty("priority", region.getPriority()); node.set("priority", region.getPriority());
node.setProperty("flags", getFlagData(region)); node.set("flags", buildFlags(region));
node.setProperty("owners", getDomainData(region.getOwners()));
node.setProperty("members", getDomainData(region.getMembers()));
Region parent = region.getParent(); Region parent = region.getParent();
if (parent != null) { if (parent != null) {
node.setProperty("parent", parent.getId()); node.set("parent", parent.getId());
} }
} }
config.setHeader("#\r\n" + config.setHeader("#\r\n" +
"# WorldGuard regions file\r\n" + "# WARNING: THIS FILE IS AUTOMATICALLY GENERATED.\r\n" +
"#\r\n" + "# A minor error in this file WILL DESTROY ALL YOUR DATA.\r\n" +
"# WARNING: THIS FILE IS AUTOMATICALLY GENERATED. If you modify this file by\r\n" +
"# hand, be aware that A SINGLE MISTYPED CHARACTER CAN CORRUPT THE FILE. If\r\n" +
"# WorldGuard is unable to parse the file, your regions will FAIL TO LOAD and\r\n" +
"# the contents of this file will reset. Please use a YAML validator such as\r\n" +
"# http://yaml-online-parser.appspot.com (for smaller files).\r\n" +
"#\r\n" + "#\r\n" +
"# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" + "# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" +
"#"); "#");
config.save(); config.save();
} }
private Map<String, Object> getFlagData(Region region) { /**
Map<String, Object> flagData = new HashMap<String, Object>(); * Read flag data from the given node and apply it to the region.
*
for (Map.Entry<Flag<?>, Object> entry : region.getFlags().entrySet()) { * @param region region to apply flags to
Flag<?> flag = entry.getKey(); * @param node node, or null
addMarshalledFlag(flagData, flag, entry.getValue()); */
private void loadFlags(Region region, ConfigurationNode node) {
if (node == null) {
return;
}
for (Flag<?> flag : DefaultFlag.getFlags()) {
Flag<?> groupFlag = flag.getRegionGroupFlag();
Object rawValue = node.get(flag.getName());
if (rawValue != null) {
setFlagFromRaw(region, flag, rawValue);
}
// Also get the group flag
if (groupFlag != null) {
rawValue = node.get(groupFlag.getName());
if (rawValue != null) {
setFlagFromRaw(region, groupFlag, rawValue);
}
}
} }
return flagData;
} }
/**
* Try to set a flag on a region from its raw value. The flag will be properly
* unmarshalled, and if that fails, then the flag won't be set.
*
* @param region region to affect
* @param flag the flag to set
* @param rawValue the raw value before unmarshalling
*/
private void setFlagFromRaw(Region region, Flag<?> flag, Object rawValue) {
Object value = flag.unmarshal(rawValue);
if (value == null) {
logger.warning("Failed to parse flag '" + flag.getName()
+ "' with value '" + rawValue.toString() + "'");
} else {
region.setFlagUnsafe(flag, flag.unmarshal(rawValue));
}
}
/**
* Build a map of flag data to store to disk.
*
* @param region the region to read the flags from
* @return map of flag data
*/
private Map<String, Object> buildFlags(Region region) {
Map<String, Object> data = new HashMap<String, Object>();
for (Map.Entry<Flag<?>, Object> entry : region.getFlags().entrySet()) {
storeFlagFromValue(data, entry.getKey(), entry.getValue());
}
return data;
}
/**
* Marshal a flag's value into something YAML-compatible.
* <p>
* if the given value is null, the flag is ignored.
*
* @param data map to put the data into
* @param flag the flag
* @param val the value, or null
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <V> void addMarshalledFlag(Map<String, Object> flagData, private <V> void storeFlagFromValue(Map<String, Object> data, Flag<V> flag, Object val) {
Flag<V> flag, Object val) {
if (val == null) { if (val == null) {
return; return;
} else {
data.put(flag.getName(), flag.marshal((V) val));
} }
flagData.put(flag.getName(), flag.marshal((V) val));
}
private Map<String, Object> getDomainData(DefaultDomain domain) {
Map<String, Object> domainData = new HashMap<String, Object>();
setDomainData(domainData, "players", domain.getPlayers());
setDomainData(domainData, "groups", domain.getGroups());
return domainData;
}
private void setDomainData(Map<String, Object> domainData,
String key, Set<String> domain) {
if (domain.size() == 0) {
return;
}
List<String> list = new ArrayList<String>();
for (String str : domain) {
list.add(str);
}
domainData.put(key, list);
} }
public Map<String, Region> getRegions() {
return regions;
}
public void setRegions(Map<String, Region> regions) {
this.regions = regions;
}
} }