/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using ThirdParty.Json.LitJson;
namespace Amazon.Runtime.Documents
{
///
/// A specialized type that is used to carry open content. A can represent primitives like
/// , , and , complex objects
/// (represented as a Dictionary<string,Document>) and lists of .
///
/// When working with a it's necessary to first convert it to its backing type via one of the provided AsX() methods.
/// Type checking can be done via IsX() method.
///
///
///
/// Document Types specification specifies support for arbitrary precision integers. However, the dotnet implementation
/// is limited to representing numbers as either , or .
///
public partial struct Document : IEquatable, IEnumerable, IEnumerable>
{
private readonly bool _dataBool;
private readonly double _dataDouble;
private readonly int _dataInt;
private readonly long _dataLong;
private readonly string _dataString;
private List _dataList;
private Dictionary _dataDictionary;
public DocumentType Type { get; private set; }
#region Constructors
public Document(bool value) : this()
{
Type = DocumentType.Bool;
_dataBool = value;
}
public Document(double value) : this()
{
Type = DocumentType.Double;
_dataDouble = value;
}
public Document(int value) : this()
{
Type = DocumentType.Int;
_dataInt = value;
}
public Document(long value) : this()
{
Type = DocumentType.Long;
_dataLong = value;
}
public Document(string value) : this()
{
Type = DocumentType.String;
_dataString = value;
}
public Document(List values) : this()
{
Type = DocumentType.List;
_dataList = values;
}
public Document(params Document[] values) : this(values.ToList()) { }
public Document(Dictionary values) : this()
{
Type = DocumentType.Dictionary;
_dataDictionary = values;
}
#endregion
#region Implicit Conversion Operators
public static implicit operator Document(bool value) => new Document(value);
public static implicit operator Document(double value) => new Document(value);
public static implicit operator Document(int value) => new Document(value);
public static implicit operator Document(long value) => new Document(value);
public static implicit operator Document(string value) => new Document(value);
public static implicit operator Document(Document[] values) => new Document(values);
public static implicit operator Document(Dictionary values) => new Document(values);
#endregion
#region Type Checking/Conversion
///
/// Returns true if is
///
public bool IsBool() => Type == DocumentType.Bool;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public bool AsBool()
{
AssertIsType(DocumentType.Bool);
return _dataBool;
}
///
/// Returns true if is
///
public bool IsDictionary() => Type == DocumentType.Dictionary;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public Dictionary AsDictionary()
{
AssertIsType(DocumentType.Dictionary);
return _dataDictionary;
}
///
/// Returns true if is
///
public bool IsDouble() => Type == DocumentType.Double;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public double AsDouble()
{
AssertIsType(DocumentType.Double);
return _dataDouble;
}
///
/// Returns true if is
///
public bool IsInt() => Type == DocumentType.Int;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public int AsInt()
{
AssertIsType(DocumentType.Int);
return _dataInt;
}
///
/// Returns true if is
///
public bool IsList() => Type == DocumentType.List;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public List AsList()
{
AssertIsType(DocumentType.List);
return _dataList;
}
///
/// Returns true if is
///
public bool IsLong() => Type == DocumentType.Long;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public long AsLong()
{
AssertIsType(DocumentType.Long);
return _dataLong;
}
///
/// Returns true if is
///
public bool IsNull() => Type == DocumentType.Null;
///
/// Returns true if is
///
public bool IsString() => Type == DocumentType.String;
///
/// Returns the Document's backing value as a .
///
/// Thrown if is not
public string AsString()
{
AssertIsType(DocumentType.String);
return _dataString;
}
private void AssertIsType(DocumentType type)
{
if (Type != type)
throw new InvalidDocumentTypeConversionException(type, Type);
}
#endregion
#region Equality
public bool Equals(Document other)
{
if (Type != other.Type)
return false;
switch (Type)
{
case DocumentType.Null:
return true;
case DocumentType.Bool:
return _dataBool == other.AsBool();
case DocumentType.Double:
return _dataDouble.Equals(other.AsDouble());
case DocumentType.Int:
return _dataInt == other.AsInt();
case DocumentType.Long:
return _dataLong == other.AsLong();
case DocumentType.String:
return _dataString.Equals(other.AsString());
// use reference equality
case DocumentType.List:
return _dataList.Equals(other.AsList());
case DocumentType.Dictionary:
return _dataDictionary.Equals(other.AsDictionary());
default:
return false;
}
}
public bool Equals(Document? other)
{
if (!other.HasValue)
return false;
return Equals(other.Value);
}
public override bool Equals(object obj)
{
return Equals(obj as Document?);
}
public override int GetHashCode() => base.GetHashCode();
public static bool operator ==(Document left, Document right) => left.Equals(right);
public static bool operator !=(Document left, Document right) => !left.Equals(right);
#endregion
#region Collection Initializers
IEnumerator IEnumerable.GetEnumerator()
{
return AsList().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
if (Type == DocumentType.List)
return AsList().GetEnumerator();
if (Type == DocumentType.Dictionary)
return AsDictionary().GetEnumerator();
return new[]{this}.GetEnumerator();
}
///
/// This method is meant to support Collection Initializers and should not be used directly.
/// Use .Add() instead.
///
/// Supports:
///
///
/// Thrown if is not
public void Add(Document document)
{
// special type case to support collection initializer
if (Type == DocumentType.Null)
{
Type = DocumentType.List;
_dataList = new List();
}
AssertIsType(DocumentType.List);
_dataList.Add(document);
}
IEnumerator> IEnumerable>.GetEnumerator()
{
return AsDictionary().GetEnumerator();
}
///
/// This method is meant to support Collection Initializers and should not be used directly.
/// Use .Add() instead.
///
/// Supports:
///
///
/// Thrown if is not
public void Add(string key, Document value)
{
// special type case to support collection initializer
if (Type == DocumentType.Null)
{
_dataDictionary = new Dictionary();
Type = DocumentType.Dictionary;
}
AssertIsType(DocumentType.Dictionary);
_dataDictionary.Add(key, value);
}
#endregion
#region ToString
public override string ToString()
{
switch (Type)
{
case DocumentType.Bool:
return _dataBool.ToString();
case DocumentType.Dictionary:
return "Document dictionary";
case DocumentType.Double:
return _dataDouble.ToString(CultureInfo.CurrentCulture);
case DocumentType.Int:
return _dataInt.ToString();
case DocumentType.List:
return "Document list";
case DocumentType.Long:
return _dataLong.ToString();
case DocumentType.Null:
return "Document null value";
case DocumentType.String:
return _dataString;
}
return base.ToString();
}
#endregion
#region FromObject
///
/// Convenience method for generating objects from a strongly typed
/// or anonymous object.
///
/// This method uses reflection to analyze and is therefor not intended
/// for performance critical work. Additionally, if is a known primitive (ie ),
/// using a constructor directly will be more performant.
///
public static Document FromObject(object o)
{
IJsonWrapper jsonData = JsonMapper.ToObject(JsonMapper.ToJson(o));
return FromObject(jsonData);
}
private static Document FromObject(IJsonWrapper jsonData)
{
switch (jsonData.GetJsonType())
{
case JsonType.None:
return new Document();
case JsonType.Boolean:
return new Document(jsonData.GetBoolean());
case JsonType.Double:
return new Document(jsonData.GetDouble());
case JsonType.Int:
return new Document(jsonData.GetInt());
case JsonType.Long:
return new Document(jsonData.GetLong());
case JsonType.String:
return new Document(jsonData.GetString());
case JsonType.Array:
return new Document(jsonData.Values.OfType