using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace UniJSON
{
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5
///
public class JsonObjectValidator : IJsonSchemaValidator
{
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.1
///
public int MaxProperties
{
get; set;
}
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.2
///
public int MinProperties
{
get; set;
}
HashSet m_required = new HashSet();
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.3
///
public HashSet Required
{
get { return m_required; }
}
Dictionary m_props;
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.4
///
public Dictionary Properties
{
get
{
if (m_props == null)
{
m_props = new Dictionary();
}
return m_props;
}
}
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.5
///
public string PatternProperties
{
get; private set;
}
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.6
///
public JsonSchema AdditionalProperties
{
get; set;
}
Dictionary m_dependencies;
///
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.7
///
public Dictionary Dependencies
{
get
{
if (m_dependencies == null)
{
m_dependencies = new Dictionary();
}
return m_dependencies;
}
}
public void AddProperty(IFileSystemAccessor fs, string key, ListTreeNode value)
{
var sub = new JsonSchema();
sub.Parse(fs, value, key);
if (Properties.ContainsKey(key))
{
if (sub.Validator != null)
{
Properties[key].Validator.Merge(sub.Validator);
}
}
else
{
Properties.Add(key, sub);
}
}
public override int GetHashCode()
{
return 6;
}
public override bool Equals(object obj)
{
var rhs = obj as JsonObjectValidator;
if (rhs == null)
{
return false;
}
if (Properties.Count != rhs.Properties.Count)
{
return false;
}
foreach (var pair in Properties)
{
JsonSchema value;
if (rhs.Properties.TryGetValue(pair.Key, out value))
{
#if true
if (!value.Equals(pair.Value))
{
Console.WriteLine(string.Format("{0} is not equals", pair.Key));
var l = pair.Value.Validator;
var r = value.Validator;
return false;
}
#else
// key name match
return true;
#endif
}
else
{
return false;
}
}
if (Required.Count != rhs.Required.Count)
{
return false;
}
if (!Required.OrderBy(x => x).SequenceEqual(rhs.Required.OrderBy(x => x)))
{
return false;
}
if (Dependencies.Count != rhs.Dependencies.Count)
{
return false;
}
foreach (var kv in Dependencies)
{
if (!kv.Value.OrderBy(x => x).SequenceEqual(rhs.Dependencies[kv.Key].OrderBy(x => x)))
{
return false;
}
}
if (AdditionalProperties == null
&& rhs.AdditionalProperties == null)
{
// ok
}
else if (AdditionalProperties == null)
{
return false;
}
else if (rhs.AdditionalProperties == null)
{
return false;
}
else
{
if (!AdditionalProperties.Equals(rhs.AdditionalProperties))
{
return false;
}
}
return true;
}
public void Merge(IJsonSchemaValidator obj)
{
var rhs = obj as JsonObjectValidator;
if (rhs == null)
{
throw new ArgumentException();
}
foreach (var x in rhs.Properties)
{
if (this.Properties.ContainsKey(x.Key))
{
this.Properties[x.Key] = x.Value;
}
else
{
this.Properties.Add(x.Key, x.Value);
}
}
foreach (var x in rhs.Required)
{
this.Required.Add(x);
}
if (rhs.AdditionalProperties != null)
{
if (AdditionalProperties != null)
{
throw new NotImplementedException();
}
AdditionalProperties = rhs.AdditionalProperties;
}
}
public bool FromJsonSchema(IFileSystemAccessor fs, string key, ListTreeNode value)
{
switch (key)
{
case "maxProperties":
MaxProperties = value.GetInt32();
return true;
case "minProperties":
MinProperties = value.GetInt32();
return true;
case "required":
{
foreach (var req in value.ArrayItems())
{
m_required.Add(req.GetString());
}
}
return true;
case "properties":
{
foreach (var prop in value.ObjectItems())
{
AddProperty(fs, prop.Key.GetString(), prop.Value);
}
}
return true;
case "patternProperties":
PatternProperties = value.GetString();
return true;
case "additionalProperties":
{
var sub = new JsonSchema();
sub.Parse(fs, value, "additionalProperties");
AdditionalProperties = sub;
}
return true;
case "dependencies":
{
foreach (var kv in value.ObjectItems())
{
Dependencies.Add(kv.Key.GetString(), kv.Value.ArrayItems().Select(x => x.GetString()).ToArray());
}
}
return true;
case "propertyNames":
return true;
}
return false;
}
public void ToJsonSchema(IFormatter f)
{
f.Key("type"); f.Value("object");
if (Properties.Count > 0)
{
f.Key("properties");
f.BeginMap(Properties.Count);
foreach (var kv in Properties)
{
f.Key(kv.Key);
kv.Value.ToJson(f);
}
f.EndMap();
}
}
static class GenericFieldView
{
public static FieldInfo[] GetFields()
{
var t = typeof(T);
return t.GetFields(BindingFlags.Instance | BindingFlags.Public);
}
public static void CreateFieldProcessors(
Func creator,
Dictionary processors
)
{
foreach (var fi in GetFields())
{
processors.Add(fi.Name, creator(fi));
}
}
}
internal class ValidationResult
{
public bool IsIgnorable;
public JsonSchemaValidationException Ex;
}
public static class GenericValidator
{
class ObjectValidator
{
delegate JsonSchemaValidationException FieldValidator(
JsonSchema s, JsonSchemaValidationContext c, T o, out bool isIgnorable);
Dictionary m_validators;
static FieldValidator CreateFieldValidator(FieldInfo fi)
{
var mi = typeof(ObjectValidator).GetMethod("_CreateFieldValidator",
BindingFlags.Static | BindingFlags.NonPublic)
;
var g = mi.MakeGenericMethod(fi.FieldType);
return GenericInvokeCallFactory.StaticFunc(g)(fi);
}
static FieldValidator _CreateFieldValidator(FieldInfo fi)
{
var getter = (Func)((t) => (U)fi.GetValue(t));
return (JsonSchema s, JsonSchemaValidationContext c, T o, out bool isIgnorable) =>
{
var v = s.Validator;
using (c.Push(fi.Name))
{
var field = getter(o);
var ex = v.Validate(c, field);
isIgnorable = ex != null && s.IsExplicitlyIgnorableValue(field);
return ex;
}
};
}
public ObjectValidator()
{
var validators = new Dictionary();
GenericFieldView.CreateFieldProcessors(
CreateFieldValidator, validators);
m_validators = validators;
}
public JsonSchemaValidationException ValidateProperty(
HashSet required,
KeyValuePair property,
JsonSchemaValidationContext c,
T o,
out bool isIgnorable
)
{
var fieldName = property.Key;
var schema = property.Value;
isIgnorable = false;
FieldValidator fv;
if (m_validators.TryGetValue(fieldName, out fv))
{
var isRequired = required != null && required.Contains(fieldName);
bool isMemberIgnorable;
var ex = fv(schema, c, o, out isMemberIgnorable);
if (ex != null)
{
isIgnorable = !isRequired && isMemberIgnorable;
if (isRequired // required fields must be checked
|| c.EnableDiagnosisForNotRequiredFields)
{
return ex;
}
}
}
return null;
}
public JsonSchemaValidationException Validate(
HashSet required,
Dictionary properties,
JsonSchemaValidationContext c, T o)
{
foreach (var kv in properties)
{
bool isIgnorable;
var ex = ValidateProperty(required, kv, c, o, out isIgnorable);
if (ex != null && !isIgnorable)
{
return ex;
}
}
return null;
}
public void ValidationResults
(HashSet required,
Dictionary properties,
JsonSchemaValidationContext c, T o,
Dictionary results)
{
foreach (var kv in properties)
{
bool isIgnorable;
var ex = ValidateProperty(required, kv, c, o, out isIgnorable);
results.Add(kv.Key, new ValidationResult {
IsIgnorable = isIgnorable,
Ex = ex,
});
}
}
}
static ObjectValidator s_validator;
static void prepareValidator()
{
if (s_validator == null)
{
s_validator = new ObjectValidator();
}
}
public static JsonSchemaValidationException Validate(HashSet required,
Dictionary properties,
JsonSchemaValidationContext c, T o)
{
prepareValidator();
return s_validator.Validate(required, properties, c, o);
}
internal static void ValidationResults(HashSet required,
Dictionary properties,
JsonSchemaValidationContext c, T o,
Dictionary results)
{
prepareValidator();
s_validator.ValidationResults(required, properties, c, o, results);
}
}
public JsonSchemaValidationException Validate(JsonSchemaValidationContext c, T o)
{
if (o == null)
{
return new JsonSchemaValidationException(c, "null");
}
if (Properties.Count < MinProperties)
{
return new JsonSchemaValidationException(c, "no properties");
}
return GenericValidator.Validate(Required, Properties, c, o);
}
static class GenericSerializer
{
class Serializer
{
delegate void FieldSerializer(JsonSchema s, JsonSchemaValidationContext c, IFormatter f, T o,
Dictionary vRes, string[] deps);
Dictionary m_serializers;
static FieldSerializer CreateFieldSerializer(FieldInfo fi)
{
var mi = typeof(Serializer).GetMethod("_CreateFieldSerializer",
BindingFlags.Static | BindingFlags.NonPublic);
var g = mi.MakeGenericMethod(fi.FieldType);
return GenericInvokeCallFactory.StaticFunc(g)(fi);
}
static FieldSerializer _CreateFieldSerializer(FieldInfo fi)
{
Func getter = t =>
{
return (U)fi.GetValue(t);
};
return (s, c, f, o, vRes, deps) =>
{
var v = s.Validator;
var field = getter(o);
if (vRes[fi.Name].Ex != null)
{
return;
}
if (deps != null)
{
foreach(var dep in deps)
{
if (vRes[dep].Ex != null)
{
return;
}
}
}
f.Key(fi.Name);
v.Serialize(f, c, field);
};
}
public Serializer()
{
var serializers = new Dictionary();
GenericFieldView.CreateFieldProcessors(
CreateFieldSerializer, serializers);
m_serializers = serializers;
}
public void Serialize(JsonObjectValidator objectValidator,
IFormatter f, JsonSchemaValidationContext c, T o)
{
// Validates fields
var validationResults = new Dictionary();
GenericValidator.ValidationResults(
objectValidator.Required, objectValidator.Properties,
c, o, validationResults);
// Serialize fields
f.BeginMap(objectValidator.Properties.Count());
foreach (var property in objectValidator.Properties)
{
var fieldName = property.Key;
var schema = property.Value;
string[] deps = null;
objectValidator.Dependencies.TryGetValue(fieldName, out deps);
FieldSerializer fs;
if (m_serializers.TryGetValue(fieldName, out fs))
{
fs(schema, c, f, o, validationResults, deps);
}
}
f.EndMap();
}
}
static FieldInfo[] s_fields;
static Serializer s_serializer;
public static void Serialize(JsonObjectValidator objectValidator,
IFormatter f, JsonSchemaValidationContext c, T value)
{
if (s_serializer == null)
{
s_serializer = new Serializer();
}
s_serializer.Serialize(objectValidator, f, c, value);
}
}
public void Serialize(IFormatter f, JsonSchemaValidationContext c, T value)
{
GenericSerializer.Serialize(this, f, c, value);
}
public static class GenericDeserializer
where S : IListTreeItem, IValue
{
delegate T Deserializer(ListTreeNode src);
static Deserializer s_d;
delegate void FieldSetter(ListTreeNode s, object o);
static FieldSetter GetFieldDeserializer(FieldInfo fi)
{
return (s, o) =>
{
var u = default(U);
s.Deserialize(ref u);
fi.SetValue(o, u);
};
}
public static U DeserializeField(JsonSchema prop, ListTreeNode s)
{
var u = default(U);
prop.Validator.Deserialize(s, ref u);
return u;
}
public static void Deserialize(ListTreeNode src, ref T dst, Dictionary props)
{
if (s_d == null)
{
var target = typeof(T);
var fields = target.GetFields(BindingFlags.Instance | BindingFlags.Public);
var fieldDeserializers = fields.ToDictionary(x => Utf8String.From(x.Name), x =>
{
/*
var mi = typeof(GenericDeserializer).GetMethod("GetFieldDeserializer",
BindingFlags.Static | BindingFlags.NonPublic);
var g = mi.MakeGenericMethod(x.FieldType);
return (FieldSetter)g.Invoke(null, new object[] { x });
*/
JsonSchema prop;
if (!props.TryGetValue(x.Name, out prop))
{
return null;
}
var mi = typeof(GenericDeserializer).GetMethod("DeserializeField",
BindingFlags.Static | BindingFlags.Public);
var g = mi.MakeGenericMethod(x.FieldType);
return (FieldSetter)((s, o) =>
{
var f = g.Invoke(null, new object[] { prop, s });
x.SetValue(o, f);
});
});
s_d = (ListTreeNode s) =>
{
if (!s.IsMap())
{
throw new ArgumentException(s.Value.ValueType.ToString());
}
// boxing
var t = (object)Activator.CreateInstance();
foreach (var kv in s.ObjectItems())
{
FieldSetter setter;
if (fieldDeserializers.TryGetValue(kv.Key.GetUtf8String(), out setter))
{
if (setter != null)
{
setter(kv.Value, t);
}
}
}
return (T)t;
};
}
dst = s_d(src);
}
}
public void Deserialize(ListTreeNode src, ref U dst)
where T : IListTreeItem, IValue
{
GenericDeserializer.Deserialize(src, ref dst, Properties);
}
}
}