#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Newtonsoft.Json.Utilities; using System.Collections; using System.Diagnostics; using System.Globalization; using System.ComponentModel; using System.Collections.Specialized; #if !SILVERLIGHT using Newtonsoft.Json.Linq.ComponentModel; #endif namespace Newtonsoft.Json.Linq { /// /// Represents a token that can contain other tokens. /// public abstract class JContainer : JToken, IList #if !SILVERLIGHT , ITypedList, IBindingList #else , IList, INotifyCollectionChanged #endif { #if !SILVERLIGHT /// /// Occurs when the list changes or an item in the list changes. /// public event ListChangedEventHandler ListChanged; /// /// Occurs before an item is added to the collection. /// public event AddingNewEventHandler AddingNew; #else /// /// Occurs when the items list of the collection has changed, or the collection is reset. /// public event NotifyCollectionChangedEventHandler CollectionChanged; #endif private JToken _content; private object _syncRoot; private bool _busy; internal JToken Content { get { return _content; } set { _content = value; } } internal JContainer() { } internal JContainer(JContainer other) { ValidationUtils.ArgumentNotNull(other, "c"); JToken content = other.Last; if (content != null) { do { content = content._next; Add(content.CloneToken()); } while (content != other.Last); } } internal void CheckReentrancy() { if (_busy) throw new InvalidOperationException("Cannot change {0} during a collection change event.".FormatWith(CultureInfo.InvariantCulture, GetType())); } #if !SILVERLIGHT /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnAddingNew(AddingNewEventArgs e) { AddingNewEventHandler handler = AddingNew; if (handler != null) handler(this, e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnListChanged(ListChangedEventArgs e) { ListChangedEventHandler handler = ListChanged; if (handler != null) { _busy = true; try { handler(this, e); } finally { _busy = false; } } } #else /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler handler = CollectionChanged; if (handler != null) { _busy = true; try { handler(this, e); } finally { _busy = false; } } } #endif /// /// Gets a value indicating whether this token has childen tokens. /// /// /// true if this token has child values; otherwise, false. /// public override bool HasValues { get { return (_content != null); } } internal bool ContentsEqual(JContainer container) { JToken t1 = First; JToken t2 = container.First; if (t1 == t2) return true; do { if (t1 == null && t2 == null) return true; if (t1 != null && t2 != null && t1.DeepEquals(t2)) { t1 = (t1 != Last) ? t1.Next : null; t2 = (t2 != container.Last) ? t2.Next : null; } else { return false; } } while (true); } /// /// Get the first child token of this token. /// /// /// A containing the first child token of the . /// public override JToken First { get { if (Last == null) return null; return Last._next; } } /// /// Get the last child token of this token. /// /// /// A containing the last child token of the . /// public override JToken Last { [DebuggerStepThrough] get { return _content; } } /// /// Returns a collection of the child tokens of this token, in document order. /// /// /// An of containing the child tokens of this , in document order. /// public override JEnumerable Children() { return new JEnumerable(ChildrenInternal()); } private IEnumerable ChildrenInternal() { JToken first = First; JToken current = first; if (current == null) yield break; do { yield return current; } while ((current = current.Next) != null); } /// /// Returns a collection of the child values of this token, in document order. /// /// The type to convert the values to. /// /// A containing the child values of this , in document order. /// public override IEnumerable Values() { return Children().Convert(); } /// /// Returns a collection of the descendant tokens for this token in document order. /// /// An containing the descendant tokens of the . public IEnumerable Descendants() { foreach (JToken o in Children()) { yield return o; JContainer c = o as JContainer; if (c != null) { foreach (JToken d in c.Descendants()) { yield return d; } } } } internal bool IsMultiContent(object content) { return (content is IEnumerable && !(content is string) && !(content is JToken) && !(content is byte[])); } internal virtual void AddItem(bool isLast, JToken previous, JToken item) { CheckReentrancy(); ValidateToken(item, null); item = EnsureParentToken(item); JToken next = (previous != null) ? previous._next : item; item.Parent = this; item.Next = next; if (previous != null) previous.Next = item; if (isLast || previous == null) _content = item; #if !SILVERLIGHT if (ListChanged != null) OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, IndexOfItem(item))); #else if (CollectionChanged != null) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, IndexOfItem(item))); #endif } internal JToken EnsureParentToken(JToken item) { if (item.Parent != null) { item = item.CloneToken(); } else { // check whether attempting to add a token to itself JContainer parent = this; while (parent.Parent != null) { parent = parent.Parent; } if (item == parent) { item = item.CloneToken(); } } return item; } internal void AddInternal(bool isLast, JToken previous, object content) { if (IsMultiContent(content)) { IEnumerable enumerable = (IEnumerable) content; JToken multiPrevious = previous; foreach (object c in enumerable) { AddInternal(isLast, multiPrevious, c); multiPrevious = (multiPrevious != null) ? multiPrevious._next : Last; } } else { JToken item = CreateFromContent(content); AddItem(isLast, previous, item); } } internal int IndexOfItem(JToken item) { int index = 0; foreach (JToken token in Children()) { if (token == item) return index; index++; } return -1; } internal virtual void InsertItem(int index, JToken item) { if (index == 0) { AddFirst(item); } else { JToken token = GetItem(index); AddInternal(false, token.Previous, item); } } internal virtual void RemoveItemAt(int index) { if (index < 0) throw new ArgumentOutOfRangeException("index", "index is less than 0."); CheckReentrancy(); int currentIndex = 0; foreach (JToken token in Children()) { if (index == currentIndex) { token.Remove(); #if !SILVERLIGHT OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index)); #else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, token, index)); #endif return; } currentIndex++; } throw new ArgumentOutOfRangeException("index", "index is equal to or greater than Count."); } internal virtual bool RemoveItem(JToken item) { if (item == null || item.Parent != this) return false; CheckReentrancy(); JToken content = _content; int itemIndex = 0; while (content._next != item) { itemIndex++; content = content._next; } if (content == item) { // token is containers last child _content = null; } else { if (_content == item) { _content = content; } content._next = item._next; } item.Parent = null; item.Next = null; #if !SILVERLIGHT OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, itemIndex)); #else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, itemIndex)); #endif return true; } internal virtual JToken GetItem(int index) { return Children().ElementAt(index); } internal virtual void SetItem(int index, JToken item) { CheckReentrancy(); JToken token = GetItem(index); token.Replace(item); #if !SILVERLIGHT OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index)); #else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, token, index)); #endif } internal virtual void ClearItems() { CheckReentrancy(); while (_content != null) { JToken o = _content; JToken next = o._next; if (o != _content || next != o._next) throw new InvalidOperationException("This operation was corrupted by external code."); if (next != o) o._next = next._next; else _content = null; next.Parent = null; next._next = null; } #if !SILVERLIGHT OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); #else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); #endif } internal virtual void ReplaceItem(JToken existing, JToken replacement) { if (existing == null || existing.Parent != this) return; if (IsTokenUnchanged(existing, replacement)) return; CheckReentrancy(); replacement = EnsureParentToken(replacement); ValidateToken(replacement, existing); JToken content = _content; int itemIndex = 0; while (content._next != existing) { itemIndex++; content = content._next; } if (content == existing) { // token is containers last child _content = replacement; replacement._next = replacement; } else { if (_content == existing) { _content = replacement; } content._next = replacement; replacement._next = existing._next; } replacement.Parent = this; existing.Parent = null; existing.Next = null; #if !SILVERLIGHT OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, itemIndex)); #else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, replacement, existing, itemIndex)); #endif } internal virtual bool ContainsItem(JToken item) { return (IndexOfItem(item) != -1); } internal virtual void CopyItemsTo(Array array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex is less than 0."); if (arrayIndex >= array.Length) throw new ArgumentException("arrayIndex is equal to or greater than the length of array."); if (CountItems() > array.Length - arrayIndex) throw new ArgumentException("The number of elements in the source JObject is greater than the available space from arrayIndex to the end of the destination array."); int index = 0; foreach (JToken token in Children()) { array.SetValue(token, arrayIndex + index); index++; } } internal virtual int CountItems() { return Children().Count(); } internal static bool IsTokenUnchanged(JToken currentValue, JToken newValue) { JValue v1 = currentValue as JValue; if (v1 != null) { // null will get turned into a JValue of type null if (v1.Type == JTokenType.Null && newValue == null) return true; return v1.Equals(newValue); } return false; } internal virtual void ValidateToken(JToken o, JToken existing) { ValidationUtils.ArgumentNotNull(o, "o"); if (o.Type == JTokenType.Property) throw new ArgumentException("Can not add {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, o.GetType(), GetType())); } /// /// Adds the specified content as children of this . /// /// The content to be added. public void Add(object content) { AddInternal(true, Last, content); } /// /// Adds the specified content as the first children of this . /// /// The content to be added. public void AddFirst(object content) { AddInternal(false, Last, content); } internal JToken CreateFromContent(object content) { if (content is JToken) return (JToken)content; return new JValue(content); } /// /// Creates an that can be used to add tokens to the . /// /// An that is ready to have content written to it. public JsonWriter CreateWriter() { return new JTokenWriter(this); } /// /// Replaces the children nodes of this token with the specified content. /// /// The content. public void ReplaceAll(object content) { ClearItems(); Add(content); } /// /// Removes the child nodes from this token. /// public void RemoveAll() { ClearItems(); } internal void ReadContentFrom(JsonReader r) { ValidationUtils.ArgumentNotNull(r, "r"); IJsonLineInfo lineInfo = r as IJsonLineInfo; JContainer parent = this; do { if (parent is JProperty && ((JProperty)parent).Value != null) { if (parent == this) return; parent = parent.Parent; } switch (r.TokenType) { case JsonToken.None: // new reader. move to actual content break; case JsonToken.StartArray: JArray a = new JArray(); a.SetLineInfo(lineInfo); parent.Add(a); parent = a; break; case JsonToken.EndArray: if (parent == this) return; parent = parent.Parent; break; case JsonToken.StartObject: JObject o = new JObject(); o.SetLineInfo(lineInfo); parent.Add(o); parent = o; break; case JsonToken.EndObject: if (parent == this) return; parent = parent.Parent; break; case JsonToken.StartConstructor: JConstructor constructor = new JConstructor(r.Value.ToString()); constructor.SetLineInfo(constructor); parent.Add(constructor); parent = constructor; break; case JsonToken.EndConstructor: if (parent == this) return; parent = parent.Parent; break; case JsonToken.String: case JsonToken.Integer: case JsonToken.Float: case JsonToken.Date: case JsonToken.Boolean: case JsonToken.Bytes: JValue v = new JValue(r.Value); v.SetLineInfo(lineInfo); parent.Add(v); break; case JsonToken.Comment: v = JValue.CreateComment(r.Value.ToString()); v.SetLineInfo(lineInfo); parent.Add(v); break; case JsonToken.Null: v = new JValue(null, JTokenType.Null); v.SetLineInfo(lineInfo); parent.Add(v); break; case JsonToken.Undefined: v = new JValue(null, JTokenType.Undefined); v.SetLineInfo(lineInfo); parent.Add(v); break; case JsonToken.PropertyName: string propertyName = r.Value.ToString(); JProperty property = new JProperty(propertyName); property.SetLineInfo(lineInfo); JObject parentObject = (JObject) parent; // handle multiple properties with the same name in JSON JProperty existingPropertyWithName = parentObject.Property(propertyName); if (existingPropertyWithName == null) parent.Add(property); else existingPropertyWithName.Replace(property); parent = property; break; default: throw new InvalidOperationException("The JsonReader should not be on a token of type {0}.".FormatWith(CultureInfo.InvariantCulture, r.TokenType)); } } while (r.Read()); } internal int ContentsHashCode() { int hashCode = 0; foreach (JToken item in Children()) { hashCode ^= item.GetDeepHashCode(); } return hashCode; } #if !SILVERLIGHT string ITypedList.GetListName(PropertyDescriptor[] listAccessors) { return string.Empty; } PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors) { JObject o = First as JObject; if (o != null) { // explicitly use constructor because compact framework has no provider JTypeDescriptor descriptor = new JTypeDescriptor(o); return descriptor.GetProperties(); } return null; } #endif #region IList Members int IList.IndexOf(JToken item) { return IndexOfItem(item); } void IList.Insert(int index, JToken item) { InsertItem(index, item); } void IList.RemoveAt(int index) { RemoveItemAt(index); } JToken IList.this[int index] { get { return GetItem(index); } set { SetItem(index, value); } } #endregion #region ICollection Members void ICollection.Add(JToken item) { Add(item); } void ICollection.Clear() { ClearItems(); } bool ICollection.Contains(JToken item) { return ContainsItem(item); } void ICollection.CopyTo(JToken[] array, int arrayIndex) { CopyItemsTo(array, arrayIndex); } int ICollection.Count { get { return CountItems(); } } bool ICollection.IsReadOnly { get { return false; } } bool ICollection.Remove(JToken item) { return RemoveItem(item); } #endregion private JToken EnsureValue(object value) { if (value == null) return null; if (value is JToken) return (JToken) value; throw new ArgumentException("Argument is not a JToken."); } #region IList Members int IList.Add(object value) { Add(EnsureValue(value)); return CountItems() - 1; } void IList.Clear() { ClearItems(); } bool IList.Contains(object value) { return ContainsItem(EnsureValue(value)); } int IList.IndexOf(object value) { return IndexOfItem(EnsureValue(value)); } void IList.Insert(int index, object value) { InsertItem(index, EnsureValue(value)); } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return false; } } void IList.Remove(object value) { RemoveItem(EnsureValue(value)); } void IList.RemoveAt(int index) { RemoveItemAt(index); } object IList.this[int index] { get { return GetItem(index); } set { SetItem(index, EnsureValue(value)); } } #endregion #region ICollection Members void ICollection.CopyTo(Array array, int index) { CopyItemsTo(array, index); } int ICollection.Count { get { return CountItems(); } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (_syncRoot == null) Interlocked.CompareExchange(ref _syncRoot, new object(), null); return _syncRoot; } } #endregion #region IBindingList Members #if !SILVERLIGHT void IBindingList.AddIndex(PropertyDescriptor property) { } object IBindingList.AddNew() { AddingNewEventArgs args = new AddingNewEventArgs(); OnAddingNew(args); if (args.NewObject == null) throw new Exception("Could not determine new value to add to '{0}'.".FormatWith(CultureInfo.InvariantCulture, GetType())); if (!(args.NewObject is JToken)) throw new Exception("New item to be added to collection must be compatible with {0}.".FormatWith(CultureInfo.InvariantCulture, typeof (JToken))); JToken newItem = (JToken)args.NewObject; Add(newItem); return newItem; } bool IBindingList.AllowEdit { get { return true; } } bool IBindingList.AllowNew { get { return true; } } bool IBindingList.AllowRemove { get { return true; } } void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) { throw new NotSupportedException(); } int IBindingList.Find(PropertyDescriptor property, object key) { throw new NotSupportedException(); } bool IBindingList.IsSorted { get { return false; } } void IBindingList.RemoveIndex(PropertyDescriptor property) { } void IBindingList.RemoveSort() { throw new NotSupportedException(); } ListSortDirection IBindingList.SortDirection { get { return ListSortDirection.Ascending; } } PropertyDescriptor IBindingList.SortProperty { get { return null; } } bool IBindingList.SupportsChangeNotification { get { return true; } } bool IBindingList.SupportsSearching { get { return false; } } bool IBindingList.SupportsSorting { get { return false; } } #endif #endregion } }