added json validation for saves
This commit is contained in:
parent
dccb12116d
commit
eeb5f9aea1
3 changed files with 813 additions and 271 deletions
|
@ -1,37 +1,44 @@
|
|||
package StevenDimDoors.mod_pocketDim.saving;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import StevenDimDoors.mod_pocketDim.Point3D;
|
||||
import StevenDimDoors.mod_pocketDim.util.BaseConfigurationProcessor;
|
||||
import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException;
|
||||
import StevenDimDoors.mod_pocketDim.util.JSONValidator;
|
||||
import StevenDimDoors.mod_pocketDim.util.Point4D;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
|
||||
import StevenDimDoors.mod_pocketDim.Point3D;
|
||||
import StevenDimDoors.mod_pocketDim.core.DDLock;
|
||||
import StevenDimDoors.mod_pocketDim.util.BaseConfigurationProcessor;
|
||||
import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException;
|
||||
import StevenDimDoors.mod_pocketDim.util.Point4D;
|
||||
|
||||
public class DimDataProcessor extends BaseConfigurationProcessor<PackedDimData>
|
||||
{
|
||||
private static final String JSON_SCHEMA_PATH = "/assets/dimdoors/text/Dim_Data_Schema.json";
|
||||
private static final JsonParser jsonParser = new JsonParser();
|
||||
|
||||
@Override
|
||||
public PackedDimData readFromStream(InputStream inputStream)
|
||||
throws ConfigurationProcessingException
|
||||
{
|
||||
try
|
||||
{
|
||||
//read in the json save file represeting a single dimension
|
||||
JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
|
||||
PackedDimData data = this.createDImDataFromJson(reader);
|
||||
PackedDimData data = this.readDimDataJson(reader);
|
||||
reader.close();
|
||||
return data;
|
||||
|
||||
}
|
||||
catch (IOException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
throw new ConfigurationProcessingException("Could not read packedDimData");
|
||||
|
@ -43,284 +50,46 @@ public class DimDataProcessor extends BaseConfigurationProcessor<PackedDimData>
|
|||
public void writeToStream(OutputStream outputStream, PackedDimData data)
|
||||
throws ConfigurationProcessingException
|
||||
{
|
||||
/** Print out dimData using the GSON built in serializer. I dont feel bad doing this because
|
||||
* 1- We can read it
|
||||
* 2- We are manually reading the data in.
|
||||
* 3- The error messages tell us exactly where its failing, so its easy to fix
|
||||
*/
|
||||
|
||||
//create a json object from a packedDimData instance
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
Gson gson = gsonBuilder.setPrettyPrinting().create();
|
||||
JsonElement ele = gson.toJsonTree(data);
|
||||
|
||||
try
|
||||
{
|
||||
outputStream.write(gson.toJson(data).getBytes("UTF-8"));
|
||||
//ensure our json object corresponds to our schema
|
||||
validateJson(ele);
|
||||
outputStream.write(data.toString().getBytes("UTF-8"));
|
||||
outputStream.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
// not sure if this is kosher, we need it to explode, but not by throwing the IO exception.
|
||||
throw new ConfigurationProcessingException("Incorrectly formatted save data");
|
||||
}
|
||||
}
|
||||
|
||||
public PackedDimData readDimDataJson(JsonReader reader) throws IOException
|
||||
{
|
||||
JsonElement ele = jsonParser.parse(reader);
|
||||
this.validateJson(ele);
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
return gsonBuilder.create().fromJson(ele, PackedDimData.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nightmare method that takes a JsonReader pointed at a serialized instance of PackedDimData
|
||||
* @param reader
|
||||
* checks our json against the dim data schema
|
||||
* @param data
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public PackedDimData createDImDataFromJson(JsonReader reader) throws IOException
|
||||
public boolean validateJson(JsonElement data) throws IOException
|
||||
{
|
||||
int ID;
|
||||
boolean IsDungeon;
|
||||
boolean IsFilled;
|
||||
int Depth;
|
||||
int PackDepth;
|
||||
int ParentID;
|
||||
int RootID;
|
||||
PackedDungeonData Dungeon = null;
|
||||
Point3D Origin;
|
||||
int Orientation;
|
||||
List<Integer> ChildIDs;
|
||||
List<PackedLinkData> Links;
|
||||
List<PackedLinkTail> Tails = new ArrayList<PackedLinkTail>();
|
||||
|
||||
reader.beginObject();
|
||||
|
||||
reader.nextName();
|
||||
if (reader.nextLong() != PackedDimData.SAVE_DATA_VERSION_ID)
|
||||
{
|
||||
throw new IOException("Save data version mismatch");
|
||||
}
|
||||
|
||||
reader.nextName();
|
||||
ID = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
IsDungeon = reader.nextBoolean();
|
||||
|
||||
reader.nextName();
|
||||
IsFilled = reader.nextBoolean();
|
||||
|
||||
reader.nextName();
|
||||
Depth = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
PackDepth = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
ParentID=reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
RootID= reader.nextInt();
|
||||
|
||||
if(reader.nextName().equals("DungeonData"))
|
||||
{
|
||||
Dungeon = createDungeonDataFromJson(reader);
|
||||
reader.nextName();
|
||||
}
|
||||
|
||||
Origin = createPointFromJson(reader);
|
||||
|
||||
reader.nextName();
|
||||
Orientation = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
ChildIDs = this.createIntListFromJson(reader);
|
||||
|
||||
reader.nextName();
|
||||
Links = this.createLinksListFromJson(reader);
|
||||
|
||||
return new PackedDimData(ID, Depth, PackDepth, ParentID, RootID, Orientation, IsDungeon, IsFilled, Dungeon, Origin, ChildIDs, Links, Tails);
|
||||
InputStream in = this.getClass().getResourceAsStream(JSON_SCHEMA_PATH);
|
||||
JsonReader reader = new JsonReader(new InputStreamReader(in));
|
||||
JSONValidator.validate((JsonObject) jsonParser.parse(reader), data);
|
||||
reader.close();
|
||||
in.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
private Point3D createPointFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
reader.beginObject();
|
||||
|
||||
reader.nextName();
|
||||
int x = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
int y = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
int z = reader.nextInt();
|
||||
|
||||
reader.endObject();
|
||||
|
||||
return new Point3D(x,y,z);
|
||||
}
|
||||
|
||||
private Point4D createPoint4DFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
reader.beginObject();
|
||||
|
||||
reader.nextName();
|
||||
int x = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
int y = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
int z = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
int dimension = reader.nextInt();
|
||||
|
||||
reader.endObject();
|
||||
|
||||
return new Point4D(x,y,z,dimension);
|
||||
}
|
||||
|
||||
private List<Integer> createIntListFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
List<Integer> list = new ArrayList<Integer>();
|
||||
reader.beginArray();
|
||||
|
||||
while (reader.peek() != JsonToken.END_ARRAY)
|
||||
{
|
||||
list.add(reader.nextInt());
|
||||
|
||||
}
|
||||
reader.endArray();
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<PackedLinkData> createLinksListFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
List<PackedLinkData> list = new ArrayList<PackedLinkData>();
|
||||
|
||||
reader.beginArray();
|
||||
|
||||
while (reader.peek() != JsonToken.END_ARRAY)
|
||||
{
|
||||
list.add(createLinkDataFromJson(reader));
|
||||
}
|
||||
reader.endArray();
|
||||
return list;
|
||||
}
|
||||
|
||||
private PackedLinkData createLinkDataFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
DDLock lock = null;
|
||||
|
||||
Point4D source;
|
||||
Point3D parent;
|
||||
PackedLinkTail tail;
|
||||
int orientation;
|
||||
List<Point3D> children = new ArrayList<Point3D>();
|
||||
|
||||
reader.beginObject();
|
||||
|
||||
reader.nextName();
|
||||
source = this.createPoint4DFromJson(reader);
|
||||
|
||||
reader.nextName();
|
||||
parent = this.createPointFromJson(reader);
|
||||
|
||||
reader.nextName();
|
||||
tail = this.createLinkTailFromJson(reader);
|
||||
|
||||
reader.nextName();
|
||||
orientation = reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
reader.beginArray();
|
||||
|
||||
while (reader.peek() != JsonToken.END_ARRAY)
|
||||
{
|
||||
children.add(this.createPointFromJson(reader));
|
||||
}
|
||||
reader.endArray();
|
||||
|
||||
if(reader.peek()== JsonToken.NAME)
|
||||
{
|
||||
lock = this.createLockFromJson(reader);
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
return new PackedLinkData(source, parent, tail, orientation, children, lock);
|
||||
}
|
||||
private PackedDungeonData createDungeonDataFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
int Weight;
|
||||
boolean IsOpen;
|
||||
boolean IsInternal;
|
||||
String SchematicPath;
|
||||
String SchematicName;
|
||||
String DungeonTypeName;
|
||||
String DungeonPackName;
|
||||
|
||||
reader.beginObject();
|
||||
@SuppressWarnings("unused")
|
||||
JsonToken test = reader.peek();
|
||||
|
||||
if(reader.peek() == JsonToken.END_OBJECT)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
reader.nextName();
|
||||
Weight=reader.nextInt();
|
||||
|
||||
reader.nextName();
|
||||
IsOpen=reader.nextBoolean();
|
||||
|
||||
reader.nextName();
|
||||
IsInternal=reader.nextBoolean();
|
||||
|
||||
reader.nextName();
|
||||
SchematicPath=reader.nextString();
|
||||
|
||||
reader.nextName();
|
||||
SchematicName=reader.nextString();
|
||||
|
||||
reader.nextName();
|
||||
DungeonTypeName=reader.nextString();
|
||||
|
||||
reader.nextName();
|
||||
DungeonPackName=reader.nextString();
|
||||
|
||||
reader.endObject();
|
||||
return new PackedDungeonData(Weight, IsOpen, IsInternal, SchematicPath, SchematicName, DungeonTypeName, DungeonPackName);
|
||||
}
|
||||
private PackedLinkTail createLinkTailFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
Point4D destination = null;
|
||||
int linkType;
|
||||
reader.beginObject();
|
||||
reader.nextName();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
JsonToken test = reader.peek();
|
||||
if (reader.peek() == JsonToken.BEGIN_OBJECT)
|
||||
{
|
||||
destination = this.createPoint4DFromJson(reader);
|
||||
reader.nextName();
|
||||
}
|
||||
|
||||
linkType = reader.nextInt();
|
||||
|
||||
reader.endObject();
|
||||
|
||||
return new PackedLinkTail(destination, linkType);
|
||||
}
|
||||
|
||||
private DDLock createLockFromJson(JsonReader reader) throws IOException
|
||||
{
|
||||
reader.nextName();
|
||||
|
||||
reader.beginObject();
|
||||
reader.nextName();
|
||||
|
||||
boolean locked = reader.nextBoolean();
|
||||
reader.nextName();
|
||||
|
||||
int key = reader.nextInt();
|
||||
reader.endObject();
|
||||
|
||||
return new DDLock(locked, key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,544 @@
|
|||
package StevenDimDoors.mod_pocketDim.util;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
public class JSONValidator
|
||||
{
|
||||
|
||||
static final public String TYPE = "type";
|
||||
static final public String ANY = "any";
|
||||
static final public String PROPERTIES = "properties";
|
||||
static final public String OPTIONAL = "optional";
|
||||
static final public String ADDITIONAL_PROPERTIES = "additionalProperties";
|
||||
static final public String MIN_LENGTH = "minLength";
|
||||
static final public String MAX_LENGTH = "maxLength";
|
||||
static final public String MINIMUM = "minimum";
|
||||
static final public String MAXIMUM = "maximum";
|
||||
static final public String PATTERN = "pattern";
|
||||
static final public String ITEMS = "items";
|
||||
static final public String ENUM = "enum";
|
||||
static final public String REQUIRED = "required";
|
||||
|
||||
|
||||
JsonObject schema;
|
||||
|
||||
public JsonObject getSchema()
|
||||
{
|
||||
return schema;
|
||||
}
|
||||
|
||||
public JSONValidator(JsonObject schema)
|
||||
{
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
static class WrongType extends JsonParseException
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
WrongType(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
|
||||
static WrongType generate(String path, Set<Type> types, Type found)
|
||||
{
|
||||
boolean first = true;
|
||||
String typeList = "'unknown'";
|
||||
for (Type type : types)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
typeList = "'" + type.getTypeString() + "'";
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
typeList += " or '" + type.getTypeString() + "'";
|
||||
}
|
||||
}
|
||||
|
||||
return new WrongType("Invalid: Expected type " + typeList + " at '" + path + "', but " + "found type '" + found.getTypeString() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
static enum Type
|
||||
{
|
||||
STRING("string"), NUMBER("number"), INTEGER("integer"), BOOLEAN("boolean"), OBJECT("object"), ARRAY("array"), NULL("null");
|
||||
|
||||
String typeString;
|
||||
|
||||
Type(String typeString)
|
||||
{
|
||||
this.typeString = typeString;
|
||||
}
|
||||
|
||||
public String getTypeString()
|
||||
{
|
||||
return typeString;
|
||||
}
|
||||
}
|
||||
|
||||
static Set<Type> anyTypeSet()
|
||||
{
|
||||
HashSet<Type> hashSet = new HashSet<Type>();
|
||||
hashSet.add(Type.STRING);
|
||||
hashSet.add(Type.NUMBER);
|
||||
hashSet.add(Type.INTEGER);
|
||||
hashSet.add(Type.BOOLEAN);
|
||||
hashSet.add(Type.OBJECT);
|
||||
hashSet.add(Type.ARRAY);
|
||||
hashSet.add(Type.NULL);
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
static Set<Type> getSimpleType(String path, String type)
|
||||
{
|
||||
for (Type t : Type.values())
|
||||
{
|
||||
if (t.getTypeString().equals(type))
|
||||
{
|
||||
if (t != Type.NUMBER)
|
||||
{
|
||||
return singleton(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
HashSet<Type> set = new HashSet<Type>();
|
||||
set.add(Type.NUMBER);
|
||||
set.add(Type.INTEGER);
|
||||
return set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ANY.equals(type))
|
||||
{
|
||||
return anyTypeSet();
|
||||
}
|
||||
|
||||
// Unknown type, spec says to allow any.
|
||||
return anyTypeSet();
|
||||
}
|
||||
|
||||
static Set<Type> getTypeSet(String path, JsonObject schema) throws JsonParseException
|
||||
{
|
||||
JsonElement typeElement = schema.get(TYPE);
|
||||
|
||||
if (typeElement == null)
|
||||
{
|
||||
// Spec says that a missing type object means accept any type.
|
||||
return anyTypeSet();
|
||||
}
|
||||
|
||||
if (typeElement.isJsonPrimitive())
|
||||
{
|
||||
JsonPrimitive primitive = typeElement.getAsJsonPrimitive();
|
||||
if (primitive.isString())
|
||||
{
|
||||
return getSimpleType(path, primitive.getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
if (typeElement.isJsonArray())
|
||||
{
|
||||
HashSet<Type> set = new HashSet<Type>();
|
||||
JsonArray array = typeElement.getAsJsonArray();
|
||||
for (JsonElement element : array)
|
||||
{
|
||||
if (element.isJsonPrimitive())
|
||||
{
|
||||
JsonPrimitive primitive = element.getAsJsonPrimitive();
|
||||
if (primitive.isString())
|
||||
{
|
||||
set.addAll(getSimpleType(path, primitive.getAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown type. Accept all.
|
||||
return anyTypeSet();
|
||||
}
|
||||
}
|
||||
|
||||
// Don't know what this is, assume any.
|
||||
return anyTypeSet();
|
||||
}
|
||||
|
||||
static Type getType(JsonElement element)
|
||||
{
|
||||
if (element.isJsonArray())
|
||||
{
|
||||
return Type.ARRAY;
|
||||
}
|
||||
if (element.isJsonObject())
|
||||
{
|
||||
return Type.OBJECT;
|
||||
}
|
||||
if (element.isJsonNull())
|
||||
{
|
||||
return Type.NULL;
|
||||
}
|
||||
JsonPrimitive primitive = element.getAsJsonPrimitive();
|
||||
if (primitive.isString())
|
||||
{
|
||||
return Type.STRING;
|
||||
}
|
||||
if (primitive.isBoolean())
|
||||
{
|
||||
return Type.BOOLEAN;
|
||||
}
|
||||
if (primitive.isNumber())
|
||||
{
|
||||
BigDecimal decimal = primitive.getAsBigDecimal();
|
||||
int scale = decimal.scale();
|
||||
if (scale > 0)
|
||||
{
|
||||
return Type.NUMBER;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Type.INTEGER;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't know. Punt and call it a string.
|
||||
return Type.STRING;
|
||||
}
|
||||
|
||||
static void validateObject(String path, JsonObject schema, JsonObject obj) throws JsonParseException
|
||||
{
|
||||
Set<String> propertiesSeen = new HashSet<String>();
|
||||
|
||||
JsonArray required = schema.getAsJsonArray(REQUIRED);
|
||||
JsonObject properties = schema.getAsJsonObject(PROPERTIES);
|
||||
|
||||
if (properties == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Map.Entry<String, JsonElement>> propertySet = properties.entrySet();
|
||||
ArrayList<String> requiredFields = new ArrayList<String>();
|
||||
|
||||
for(JsonElement st : required.getAsJsonArray())
|
||||
{
|
||||
requiredFields.add(st.getAsString());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, JsonElement> property : propertySet)
|
||||
{
|
||||
String name = property.getKey();
|
||||
String newPath = path + "['" + name + "']";
|
||||
JsonElement element = property.getValue();
|
||||
propertiesSeen.add(name);
|
||||
|
||||
if (!element.isJsonObject())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: property definition not an object at '" + newPath + "'");
|
||||
}
|
||||
|
||||
JsonObject definition = element.getAsJsonObject();
|
||||
|
||||
JsonElement newTarget = obj.get(name);
|
||||
|
||||
if (newTarget == null)
|
||||
{
|
||||
JsonPrimitive optional = definition.getAsJsonPrimitive(OPTIONAL);
|
||||
boolean needed = ((optional==null) && requiredFields.contains(name)) || (optional != null && !optional.getAsBoolean());
|
||||
|
||||
if (needed)
|
||||
{
|
||||
throw new JsonParseException("Invalid: Required property '" + newPath + "' not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
validate(newPath, definition, newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
JsonElement additionalProperties = schema.get(ADDITIONAL_PROPERTIES);
|
||||
JsonObject additionalSchema = null;
|
||||
if (additionalProperties == null)
|
||||
{
|
||||
additionalSchema = new JsonObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (additionalProperties.isJsonObject())
|
||||
{
|
||||
additionalSchema = additionalProperties.getAsJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if (additionalSchema == null) {
|
||||
* logger.debug("No additional schema for '"+path+"'"); } else {
|
||||
* logger.debug("Additional schema for '"+path+"': "+
|
||||
* additionalSchema.toString()); }
|
||||
*/
|
||||
|
||||
Set<Map.Entry<String, JsonElement>> objectProperties = obj.entrySet();
|
||||
for (Map.Entry<String, JsonElement> property : objectProperties)
|
||||
{
|
||||
String name = property.getKey();
|
||||
String newPath = path + "['" + name + "']";
|
||||
if (!propertiesSeen.contains(name))
|
||||
{
|
||||
if (additionalSchema == null)
|
||||
{
|
||||
throw new JsonParseException("Invalid: Found additional property '" + newPath + "'");
|
||||
}
|
||||
validate(newPath, additionalSchema, property.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Integer getInt(String path, String attributeName, JsonObject schema) throws JsonParseException
|
||||
{
|
||||
JsonElement attributeElement = schema.get(attributeName);
|
||||
|
||||
if (attributeElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!attributeElement.isJsonPrimitive())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not an integer at '" + path + "'");
|
||||
}
|
||||
JsonPrimitive attributePrimitive = attributeElement.getAsJsonPrimitive();
|
||||
if (!attributePrimitive.isNumber())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not an integer at '" + path + "'");
|
||||
}
|
||||
|
||||
return attributePrimitive.getAsInt();
|
||||
}
|
||||
|
||||
static String getString(String path, String attributeName, JsonObject schema) throws JsonParseException
|
||||
{
|
||||
JsonElement attributeElement = schema.get(attributeName);
|
||||
|
||||
if (attributeElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!attributeElement.isJsonPrimitive())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a string at '" + path + "'");
|
||||
}
|
||||
JsonPrimitive attributePrimitive = attributeElement.getAsJsonPrimitive();
|
||||
if (!attributePrimitive.isString())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a string at '" + path + "'");
|
||||
}
|
||||
|
||||
return attributePrimitive.getAsString();
|
||||
}
|
||||
|
||||
static BigDecimal getBigDecimal(String path, String attributeName, JsonObject schema) throws JsonParseException
|
||||
{
|
||||
JsonElement attributeElement = schema.get(attributeName);
|
||||
|
||||
if (attributeElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!attributeElement.isJsonPrimitive())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a number at '" + path + "'");
|
||||
}
|
||||
JsonPrimitive attributePrimitive = attributeElement.getAsJsonPrimitive();
|
||||
if (!attributePrimitive.isNumber())
|
||||
{
|
||||
throw new JsonParseException("Bad Schema: '" + attributeName + "' attribute is not a number at '" + path + "'");
|
||||
}
|
||||
|
||||
return attributePrimitive.getAsBigDecimal();
|
||||
}
|
||||
|
||||
static void validateString(String path, JsonObject schema, String str) throws JsonParseException
|
||||
{
|
||||
Integer minLength = getInt(path, MIN_LENGTH, schema);
|
||||
Integer maxLength = getInt(path, MAX_LENGTH, schema);
|
||||
|
||||
if ((minLength != null) && (str.length() < minLength))
|
||||
{
|
||||
throw new JsonParseException("Invalid: String '" + path + "' is too short. The string needs to be more than " + minLength + " characters");
|
||||
}
|
||||
|
||||
if ((maxLength != null) && (str.length() > maxLength))
|
||||
{
|
||||
throw new JsonParseException("Invalid: String '" + path + "' is too long. The string needs to be less than " + maxLength + " characters");
|
||||
}
|
||||
|
||||
String pattern = getString(path, PATTERN, schema);
|
||||
if ((pattern != null) && (!str.matches(pattern)))
|
||||
{
|
||||
throw new JsonParseException("Invalid: String '" + path + "' does not match pattern '" + pattern + "'");
|
||||
}
|
||||
}
|
||||
|
||||
static void validateTuple(String path, JsonArray tupleSchema, JsonObject additionalSchema, JsonArray array) throws JsonParseException
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static void validateArray(String path, JsonObject schema, JsonArray array) throws JsonParseException
|
||||
{
|
||||
JsonElement additionalProperties = schema.get(ADDITIONAL_PROPERTIES);
|
||||
JsonObject additionalSchema = null;
|
||||
if (additionalProperties == null)
|
||||
{
|
||||
additionalSchema = new JsonObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (additionalProperties.isJsonObject())
|
||||
{
|
||||
additionalSchema = additionalProperties.getAsJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
JsonElement itemsElement = schema.get(ITEMS);
|
||||
if (itemsElement == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemsElement.isJsonArray())
|
||||
{
|
||||
validateTuple(path, itemsElement.getAsJsonArray(), additionalSchema, array);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject itemsSchema = null;
|
||||
if (itemsElement.isJsonObject())
|
||||
{
|
||||
itemsSchema = itemsElement.getAsJsonObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bogus items parameter, assume everything is valid.
|
||||
itemsSchema = new JsonObject();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (JsonElement element : array)
|
||||
{
|
||||
++i;
|
||||
String curPath = path + "[" + i + "]";
|
||||
validate(curPath, itemsSchema, element);
|
||||
}
|
||||
}
|
||||
|
||||
static void validateEnum(String path, JsonObject schema, JsonElement element) throws JsonParseException
|
||||
{
|
||||
JsonElement enumElement = schema.get(ENUM);
|
||||
if (enumElement == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enumElement.isJsonArray())
|
||||
{}
|
||||
|
||||
JsonArray enumArray = enumElement.getAsJsonArray();
|
||||
|
||||
for (JsonElement curElement : enumArray)
|
||||
{
|
||||
if (element.equals(curElement))
|
||||
{
|
||||
// We found a valid value.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new JsonParseException("Invalid: Property '" + path + "' is not one of the enum values.");
|
||||
}
|
||||
|
||||
static void validateNumber(String path, JsonObject schema, BigDecimal number) throws JsonParseException
|
||||
{
|
||||
BigDecimal minimum = getBigDecimal(path, MINIMUM, schema);
|
||||
if (minimum != null)
|
||||
{
|
||||
if (number.compareTo(minimum) < 0)
|
||||
{
|
||||
throw new JsonParseException("Invalid: Property '" + path + "' has a value of '" + number + "' which is less than the minimum of '" + minimum
|
||||
+ "'.");
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal maximum = getBigDecimal(path, MAXIMUM, schema);
|
||||
if (maximum != null)
|
||||
{
|
||||
if (number.compareTo(maximum) > 0)
|
||||
{
|
||||
throw new JsonParseException("Invalid: Property '" + path + "' has a value of '" + number + "' which is greater than the maximum of '"
|
||||
+ maximum + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void validate(String path, JsonObject schema, JsonElement element) throws JsonParseException
|
||||
{
|
||||
Set<Type> typeSet = getTypeSet(path, schema);
|
||||
|
||||
Type type = getType(element);
|
||||
if (!typeSet.contains(type))
|
||||
{
|
||||
throw WrongType.generate(path, typeSet, type);
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case BOOLEAN:
|
||||
case NULL:
|
||||
break;
|
||||
case NUMBER:
|
||||
case INTEGER:
|
||||
validateNumber(path, schema, element.getAsBigDecimal());
|
||||
break;
|
||||
case ARRAY:
|
||||
validateArray(path, schema, element.getAsJsonArray());
|
||||
break;
|
||||
case STRING:
|
||||
validateString(path, schema, element.getAsString());
|
||||
break;
|
||||
case OBJECT:
|
||||
validateObject(path, schema, element.getAsJsonObject());
|
||||
break;
|
||||
default:
|
||||
// Unknown type
|
||||
throw new JsonParseException("Internal Error");
|
||||
}
|
||||
|
||||
validateEnum(path, schema, element);
|
||||
}
|
||||
|
||||
static public void validate(JsonObject schema, JsonElement element) throws JsonParseException
|
||||
{
|
||||
validate("$", schema, element);
|
||||
}
|
||||
|
||||
public void validate(JsonElement element) throws JsonParseException
|
||||
{
|
||||
validate(getSchema(), element);
|
||||
}
|
||||
}
|
229
src/main/resources/assets/dimdoors/text/Dim_Data_Schema.json
Normal file
229
src/main/resources/assets/dimdoors/text/Dim_Data_Schema.json
Normal file
|
@ -0,0 +1,229 @@
|
|||
{
|
||||
"type":"object",
|
||||
"$schema": "http://json-schema.org/draft-04/schema",
|
||||
|
||||
"description": "A serialized Dim Data object",
|
||||
"properties":{
|
||||
"ChildIDs": {
|
||||
"type":"array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"Depth": {
|
||||
"type":"number"
|
||||
},
|
||||
"ID": {
|
||||
"type":"number"
|
||||
},
|
||||
"IsDungeon": {
|
||||
"type":"boolean"
|
||||
},
|
||||
"IsFilled": {
|
||||
"type":"boolean"
|
||||
},
|
||||
"DungeonData": {
|
||||
"type": "object",
|
||||
|
||||
"properties": {
|
||||
"Weight": {
|
||||
"type": "number"
|
||||
},
|
||||
"IsOpen": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"IsInternal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"SchematicPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"SchematicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"DungeonTypeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"DungeonPackName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Weight",
|
||||
"IsOpen",
|
||||
"IsInternal",
|
||||
"SchematicPath",
|
||||
"SchematicName",
|
||||
"DungeonTypeName",
|
||||
"DungeonPackName"
|
||||
]
|
||||
},
|
||||
"Links": {
|
||||
"type":"array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items":{
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"orientation": {
|
||||
"type": "number"
|
||||
},
|
||||
"source": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "integer"
|
||||
},
|
||||
"y": {
|
||||
"type": "integer"
|
||||
},
|
||||
"z": {
|
||||
"type": "integer"
|
||||
},
|
||||
"dimension": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"dimension"
|
||||
]
|
||||
},
|
||||
"parent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "integer"
|
||||
},
|
||||
"y": {
|
||||
"type": "integer"
|
||||
},
|
||||
"z": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"x",
|
||||
"y",
|
||||
"z"
|
||||
]
|
||||
},
|
||||
"tail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"linkType" : {
|
||||
"type": "number"
|
||||
},
|
||||
"destination":{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "integer"
|
||||
},
|
||||
"y": {
|
||||
"type": "integer"
|
||||
},
|
||||
"z": {
|
||||
"type": "integer"
|
||||
},
|
||||
"dimension": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"dimension"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"linkType"
|
||||
]
|
||||
},
|
||||
"lock":{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lockState": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lockKey": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lockState",
|
||||
"lockKey"
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"children",
|
||||
"orientation",
|
||||
"source",
|
||||
"parent",
|
||||
"tail"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Orientation": {
|
||||
"type":"number"
|
||||
},
|
||||
"Origin": {
|
||||
"type":"object",
|
||||
"properties":{
|
||||
"x": {
|
||||
"type":"number"
|
||||
},
|
||||
"y": {
|
||||
"type":"number"
|
||||
},
|
||||
"z": {
|
||||
"type":"number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"x",
|
||||
"y",
|
||||
"z"
|
||||
]
|
||||
},
|
||||
"PackDepth": {
|
||||
"type":"number"
|
||||
},
|
||||
"ParentID": {
|
||||
"type":"number"
|
||||
},
|
||||
"RootID": {
|
||||
"type":"number"
|
||||
},
|
||||
"SAVE_DATA_VERSION_ID_INSTANCE": {
|
||||
"type":"number"
|
||||
},
|
||||
"Tails": {
|
||||
"type":"array"
|
||||
}
|
||||
},
|
||||
"required": ["Tails",
|
||||
"SAVE_DATA_VERSION_ID_INSTANCE",
|
||||
"RootID",
|
||||
"ParentID",
|
||||
"PackDepth",
|
||||
"Origin",
|
||||
"Orientation",
|
||||
"Links",
|
||||
"IsFilled",
|
||||
"IsDungeon",
|
||||
"ID",
|
||||
"Depth",
|
||||
"ChildIDs"
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue