using System; using System.Collections.Generic; using Substrate.Core; namespace Substrate.Nbt { /// /// Indicates how an event processor should respond to returning event handler. /// public enum TagEventCode { /// /// The event processor should process the next event in the chian. /// NEXT, /// /// The event processor should ignore the verification failure and stop processing any remaining events. /// PASS, /// /// The event processor should fail and stop processing any remaining events. /// FAIL, } /// /// Event arguments for failure events. /// public class TagEventArgs : EventArgs { private string _tagName; private TagNode _parent; private TagNode _tag; private SchemaNode _schema; /// /// Gets the expected name of the referenced by this event. /// public string TagName { get { return _tagName; } } /// /// Gets the parent of the referenced by this event, if it exists. /// public TagNode Parent { get { return _parent; } } /// /// Gets the referenced by this event. /// public TagNode Tag { get { return _tag; } } /// /// Gets the corresponding to the referenced by this event. /// public SchemaNode Schema { get { return _schema; } } /// /// Constructs a new event argument set. /// /// The expected name of a . public TagEventArgs (string tagName) : base() { _tagName = tagName; } /// /// Constructs a new event argument set. /// /// The expected name of a . /// The involved in this event. public TagEventArgs (string tagName, TagNode tag) : base() { _tag = tag; _tagName = tagName; } /// /// Constructs a new event argument set. /// /// The corresponding to the involved in this event. /// The involved in this event. public TagEventArgs (SchemaNode schema, TagNode tag) : base() { _tag = tag; _schema = schema; } } /// /// An event handler for intercepting and responding to verification failures of NBT trees. /// /// Information relating to a verification event. /// A determining how the event processor should respond. public delegate TagEventCode VerifierEventHandler (TagEventArgs eventArgs); /// /// Verifies the integrity of an NBT tree against a schema definition. /// public class NbtVerifier { private TagNode _root; private SchemaNode _schema; /// /// An event that gets fired whenever an expected is not found. /// public static event VerifierEventHandler MissingTag; /// /// An event that gets fired whenever an expected is of the wrong type and cannot be cast. /// public static event VerifierEventHandler InvalidTagType; /// /// An event that gets fired whenever an expected has a value that violates the schema. /// public static event VerifierEventHandler InvalidTagValue; private Dictionary _scratch = new Dictionary(); /// /// Constructs a new object for a given NBT tree and schema. /// /// A representing the root of an NBT tree. /// A representing the root of a schema definition for the NBT tree. public NbtVerifier (TagNode root, SchemaNode schema) { _root = root; _schema = schema; } /// /// Invokes the verifier. /// /// Status indicating whether the NBT tree is valid for the given schema. public virtual bool Verify () { return Verify(null, _root, _schema); } private bool Verify (TagNode parent, TagNode tag, SchemaNode schema) { if (tag == null) { return OnMissingTag(new TagEventArgs(schema.Name)); } SchemaNodeScaler scaler = schema as SchemaNodeScaler; if (scaler != null) { return VerifyScaler(tag, scaler); } SchemaNodeString str = schema as SchemaNodeString; if (str != null) { return VerifyString(tag, str); } SchemaNodeArray array = schema as SchemaNodeArray; if (array != null) { return VerifyArray(tag, array); } SchemaNodeIntArray intarray = schema as SchemaNodeIntArray; if (intarray != null) { return VerifyIntArray(tag, intarray); } SchemaNodeList list = schema as SchemaNodeList; if (list != null) { return VerifyList(tag, list); } SchemaNodeCompound compound = schema as SchemaNodeCompound; if (compound != null) { return VerifyCompound(tag, compound); } return OnInvalidTagType(new TagEventArgs(schema.Name, tag)); } private bool VerifyScaler (TagNode tag, SchemaNodeScaler schema) { if (!tag.IsCastableTo(schema.Type)) { if (!OnInvalidTagType(new TagEventArgs(schema.Name, tag))) { return false; } } return true; } private bool VerifyString (TagNode tag, SchemaNodeString schema) { TagNodeString stag = tag as TagNodeString; if (stag == null) { if (!OnInvalidTagType(new TagEventArgs(schema, tag))) { return false; } } if (schema.Length > 0 && stag.Length > schema.Length) { if (!OnInvalidTagValue(new TagEventArgs(schema, tag))) { return false; } } if (schema.Value != null && stag.Data != schema.Value) { if (!OnInvalidTagValue(new TagEventArgs(schema, tag))) { return false; } } return true; } private bool VerifyArray (TagNode tag, SchemaNodeArray schema) { TagNodeByteArray atag = tag as TagNodeByteArray; if (atag == null) { if (!OnInvalidTagType(new TagEventArgs(schema, tag))) { return false; } } if (schema.Length > 0 && atag.Length != schema.Length) { if (!OnInvalidTagValue(new TagEventArgs(schema, tag))) { return false; } } return true; } private bool VerifyIntArray (TagNode tag, SchemaNodeIntArray schema) { TagNodeIntArray atag = tag as TagNodeIntArray; if (atag == null) { if (!OnInvalidTagType(new TagEventArgs(schema, tag))) { return false; } } if (schema.Length > 0 && atag.Length != schema.Length) { if (!OnInvalidTagValue(new TagEventArgs(schema, tag))) { return false; } } return true; } private bool VerifyList (TagNode tag, SchemaNodeList schema) { TagNodeList ltag = tag as TagNodeList; if (ltag == null) { if (!OnInvalidTagType(new TagEventArgs(schema, tag))) { return false; } } if (ltag.Count > 0 && ltag.ValueType != schema.Type) { if (!OnInvalidTagValue(new TagEventArgs(schema, tag))) { return false; } } if (schema.Length > 0 && ltag.Count != schema.Length) { if (!OnInvalidTagValue(new TagEventArgs(schema, tag))) { return false; } } // Patch up empty lists //if (schema.Length == 0) { // tag = new NBT_List(schema.Type); //} bool pass = true; // If a subschema is set, test all items in list against it if (schema.SubSchema != null) { foreach (TagNode v in ltag) { pass = Verify(tag, v, schema.SubSchema) && pass; } } return pass; } private bool VerifyCompound (TagNode tag, SchemaNodeCompound schema) { TagNodeCompound ctag = tag as TagNodeCompound; if (ctag == null) { if (!OnInvalidTagType(new TagEventArgs(schema, tag))) { return false; } } bool pass = true; foreach (SchemaNode node in schema) { TagNode value; ctag.TryGetValue(node.Name, out value); if (value == null) { if ((node.Options & SchemaOptions.CREATE_ON_MISSING) == SchemaOptions.CREATE_ON_MISSING) { _scratch[node.Name] = node.BuildDefaultTree(); continue; } else if ((node.Options & SchemaOptions.OPTIONAL) == SchemaOptions.OPTIONAL) { continue; } } pass = Verify(tag, value, node) && pass; } foreach (KeyValuePair item in _scratch) { ctag[item.Key] = item.Value; } _scratch.Clear(); return pass; } #region Event Handlers /// /// Processes registered events for whenever an expected is not found. /// /// Arguments for this event. /// Status indicating whether this event can be ignored. protected virtual bool OnMissingTag (TagEventArgs e) { if (MissingTag != null) { foreach (VerifierEventHandler func in MissingTag.GetInvocationList()) { TagEventCode code = func(e); switch (code) { case TagEventCode.FAIL: return false; case TagEventCode.PASS: return true; } } } return false; } /// /// Processes registered events for whenever an expected is of the wrong type and cannot be cast. /// /// Arguments for this event. /// Status indicating whether this event can be ignored. protected virtual bool OnInvalidTagType (TagEventArgs e) { if (InvalidTagType != null) { foreach (VerifierEventHandler func in InvalidTagType.GetInvocationList()) { TagEventCode code = func(e); switch (code) { case TagEventCode.FAIL: return false; case TagEventCode.PASS: return true; } } } return false; } /// /// Processes registered events for whenever an expected has a value that violates the schema. /// /// Arguments for this event. /// Status indicating whether this event can be ignored. protected virtual bool OnInvalidTagValue (TagEventArgs e) { if (InvalidTagValue != null) { foreach (VerifierEventHandler func in InvalidTagValue.GetInvocationList()) { TagEventCode code = func(e); switch (code) { case TagEventCode.FAIL: return false; case TagEventCode.PASS: return true; } } } return false; } #endregion } }