2 // Copyright (c) 2007 James Newton-King
4 // Permission is hereby granted, free of charge, to any person
5 // obtaining a copy of this software and associated documentation
6 // files (the "Software"), to deal in the Software without
7 // restriction, including without limitation the rights to use,
8 // copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the
10 // Software is furnished to do so, subject to the following
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 // OTHER DEALINGS IN THE SOFTWARE.
27 using System.Collections.Generic;
29 using System.Threading;
30 using Newtonsoft.Json.Utilities;
31 using System.Collections;
32 using System.Diagnostics;
33 using System.Globalization;
34 using System.ComponentModel;
35 using System.Collections.Specialized;
37 using Newtonsoft.Json.Linq.ComponentModel;
40 namespace Newtonsoft.Json.Linq
43 /// Represents a token that can contain other tokens.
45 public abstract class JContainer : JToken, IList<JToken>
47 , ITypedList, IBindingList
49 , IList, INotifyCollectionChanged
51 #if !(SILVERLIGHT || NET20 || NET35)
52 , INotifyCollectionChanged
57 /// Occurs when the list changes or an item in the list changes.
59 public event ListChangedEventHandler ListChanged;
62 /// Occurs before an item is added to the collection.
64 public event AddingNewEventHandler AddingNew;
66 #if SILVERLIGHT || !(NET20 || NET35)
68 /// Occurs when the items list of the collection has changed, or the collection is reset.
70 public event NotifyCollectionChangedEventHandler CollectionChanged;
73 private JToken _content;
74 private object _syncRoot;
77 internal JToken Content
79 get { return _content; }
80 set { _content = value; }
87 internal JContainer(JContainer other)
89 ValidationUtils.ArgumentNotNull(other, "c");
91 JToken content = other.Last;
96 content = content._next;
97 Add(content.CloneToken());
99 while (content != other.Last);
103 internal void CheckReentrancy()
106 throw new InvalidOperationException("Cannot change {0} during a collection change event.".FormatWith(CultureInfo.InvariantCulture, GetType()));
111 /// Raises the <see cref="AddingNew"/> event.
113 /// <param name="e">The <see cref="AddingNewEventArgs"/> instance containing the event data.</param>
114 protected virtual void OnAddingNew(AddingNewEventArgs e)
116 AddingNewEventHandler handler = AddingNew;
122 /// Raises the <see cref="ListChanged"/> event.
124 /// <param name="e">The <see cref="ListChangedEventArgs"/> instance containing the event data.</param>
125 protected virtual void OnListChanged(ListChangedEventArgs e)
127 ListChangedEventHandler handler = ListChanged;
143 #if SILVERLIGHT || !(NET20 || NET35)
145 /// Raises the <see cref="CollectionChanged"/> event.
147 /// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
148 protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
150 NotifyCollectionChangedEventHandler handler = CollectionChanged;
168 /// Gets a value indicating whether this token has childen tokens.
171 /// <c>true</c> if this token has child values; otherwise, <c>false</c>.
173 public override bool HasValues
175 get { return (_content != null); }
178 internal bool ContentsEqual(JContainer container)
181 JToken t2 = container.First;
188 if (t1 == null && t2 == null)
191 if (t1 != null && t2 != null && t1.DeepEquals(t2))
193 t1 = (t1 != Last) ? t1.Next : null;
194 t2 = (t2 != container.Last) ? t2.Next : null;
205 /// Get the first child token of this token.
208 /// A <see cref="JToken"/> containing the first child token of the <see cref="JToken"/>.
210 public override JToken First
222 /// Get the last child token of this token.
225 /// A <see cref="JToken"/> containing the last child token of the <see cref="JToken"/>.
227 public override JToken Last
229 [DebuggerStepThrough]
230 get { return _content; }
234 /// Returns a collection of the child tokens of this token, in document order.
237 /// An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> containing the child tokens of this <see cref="JToken"/>, in document order.
239 public override JEnumerable<JToken> Children()
241 return new JEnumerable<JToken>(ChildrenInternal());
244 internal IEnumerable<JToken> ChildrenInternal()
246 JToken first = First;
247 JToken current = first;
253 yield return current;
255 while ((current = current.Next) != null);
259 /// Returns a collection of the child values of this token, in document order.
261 /// <typeparam name="T">The type to convert the values to.</typeparam>
263 /// A <see cref="IEnumerable{T}"/> containing the child values of this <see cref="JToken"/>, in document order.
265 public override IEnumerable<T> Values<T>()
267 return Children().Convert<JToken, T>();
271 /// Returns a collection of the descendant tokens for this token in document order.
273 /// <returns>An <see cref="IEnumerable{JToken}"/> containing the descendant tokens of the <see cref="JToken"/>.</returns>
274 public IEnumerable<JToken> Descendants()
276 foreach (JToken o in Children())
279 JContainer c = o as JContainer;
282 foreach (JToken d in c.Descendants())
290 internal bool IsMultiContent(object content)
292 return (content is IEnumerable && !(content is string) && !(content is JToken) && !(content is byte[]));
295 internal virtual void AddItem(bool isLast, JToken previous, JToken item)
299 ValidateToken(item, null);
301 item = EnsureParentToken(item);
303 JToken next = (previous != null) ? previous._next : item;
308 if (previous != null)
309 previous.Next = item;
311 if (isLast || previous == null)
315 if (ListChanged != null)
316 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, IndexOfItem(item)));
318 #if SILVERLIGHT || !(NET20 || NET35)
319 if (CollectionChanged != null)
320 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, IndexOfItem(item)));
324 internal JToken EnsureParentToken(JToken item)
326 if (item.Parent != null)
328 item = item.CloneToken();
332 // check whether attempting to add a token to itself
333 JContainer parent = this;
334 while (parent.Parent != null)
336 parent = parent.Parent;
340 item = item.CloneToken();
346 internal void AddInternal(bool isLast, JToken previous, object content)
348 if (IsMultiContent(content))
350 IEnumerable enumerable = (IEnumerable) content;
352 JToken multiPrevious = previous;
353 foreach (object c in enumerable)
355 AddInternal(isLast, multiPrevious, c);
356 multiPrevious = (multiPrevious != null) ? multiPrevious._next : Last;
361 JToken item = CreateFromContent(content);
363 AddItem(isLast, previous, item);
367 internal int IndexOfItem(JToken item)
370 foreach (JToken token in Children())
381 internal virtual void InsertItem(int index, JToken item)
389 JToken token = GetItem(index);
390 AddInternal(false, token.Previous, item);
394 internal virtual void RemoveItemAt(int index)
397 throw new ArgumentOutOfRangeException("index", "index is less than 0.");
401 int currentIndex = 0;
402 foreach (JToken token in Children())
404 if (index == currentIndex)
409 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
411 #if SILVERLIGHT || !(NET20 || NET35)
412 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, token, index));
421 throw new ArgumentOutOfRangeException("index", "index is equal to or greater than Count.");
424 internal virtual bool RemoveItem(JToken item)
426 if (item == null || item.Parent != this)
431 JToken content = _content;
434 while (content._next != item)
437 content = content._next;
441 // token is containers last child
446 if (_content == item)
450 content._next = item._next;
456 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, itemIndex));
458 #if SILVERLIGHT || !(NET20 || NET35)
459 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, itemIndex));
465 internal virtual JToken GetItem(int index)
467 return Children().ElementAt(index);
470 internal virtual void SetItem(int index, JToken item)
474 JToken token = GetItem(index);
478 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
480 #if SILVERLIGHT || !(NET20 || NET35)
481 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, token, index));
485 internal virtual void ClearItems()
489 while (_content != null)
493 JToken next = o._next;
494 if (o != _content || next != o._next)
495 throw new InvalidOperationException("This operation was corrupted by external code.");
498 o._next = next._next;
507 OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
509 #if SILVERLIGHT || !(NET20 || NET35)
510 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
514 internal virtual void ReplaceItem(JToken existing, JToken replacement)
516 if (existing == null || existing.Parent != this)
519 if (IsTokenUnchanged(existing, replacement))
524 replacement = EnsureParentToken(replacement);
526 ValidateToken(replacement, existing);
528 JToken content = _content;
531 while (content._next != existing)
534 content = content._next;
537 if (content == existing)
539 // token is containers last child
540 _content = replacement;
541 replacement._next = replacement;
545 if (_content == existing)
547 _content = replacement;
549 content._next = replacement;
550 replacement._next = existing._next;
553 replacement.Parent = this;
555 existing.Parent = null;
556 existing.Next = null;
559 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, itemIndex));
561 #if SILVERLIGHT || !(NET20 || NET35)
562 OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, replacement, existing, itemIndex));
566 internal virtual bool ContainsItem(JToken item)
568 return (IndexOfItem(item) != -1);
571 internal virtual void CopyItemsTo(Array array, int arrayIndex)
574 throw new ArgumentNullException("array");
576 throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex is less than 0.");
577 if (arrayIndex >= array.Length)
578 throw new ArgumentException("arrayIndex is equal to or greater than the length of array.");
579 if (CountItems() > array.Length - arrayIndex)
580 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.");
583 foreach (JToken token in Children())
585 array.SetValue(token, arrayIndex + index);
590 internal virtual int CountItems()
592 return Children().Count();
595 internal static bool IsTokenUnchanged(JToken currentValue, JToken newValue)
597 JValue v1 = currentValue as JValue;
600 // null will get turned into a JValue of type null
601 if (v1.Type == JTokenType.Null && newValue == null)
604 return v1.Equals(newValue);
610 internal virtual void ValidateToken(JToken o, JToken existing)
612 ValidationUtils.ArgumentNotNull(o, "o");
614 if (o.Type == JTokenType.Property)
615 throw new ArgumentException("Can not add {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, o.GetType(), GetType()));
619 /// Adds the specified content as children of this <see cref="JToken"/>.
621 /// <param name="content">The content to be added.</param>
622 public void Add(object content)
624 AddInternal(true, Last, content);
628 /// Adds the specified content as the first children of this <see cref="JToken"/>.
630 /// <param name="content">The content to be added.</param>
631 public void AddFirst(object content)
633 AddInternal(false, Last, content);
636 internal JToken CreateFromContent(object content)
638 if (content is JToken)
639 return (JToken)content;
641 return new JValue(content);
645 /// Creates an <see cref="JsonWriter"/> that can be used to add tokens to the <see cref="JToken"/>.
647 /// <returns>An <see cref="JsonWriter"/> that is ready to have content written to it.</returns>
648 public JsonWriter CreateWriter()
650 return new JTokenWriter(this);
654 /// Replaces the children nodes of this token with the specified content.
656 /// <param name="content">The content.</param>
657 public void ReplaceAll(object content)
664 /// Removes the child nodes from this token.
666 public void RemoveAll()
671 internal void ReadTokenFrom(JsonReader r)
673 int startDepth = r.Depth;
676 throw new Exception("Error reading {0} from JsonReader.".FormatWith(CultureInfo.InvariantCulture, GetType().Name));
680 int endDepth = r.Depth;
682 if (endDepth > startDepth)
683 throw new Exception("Unexpected end of content while loading {0}.".FormatWith(CultureInfo.InvariantCulture, GetType().Name));
686 internal void ReadContentFrom(JsonReader r)
688 ValidationUtils.ArgumentNotNull(r, "r");
689 IJsonLineInfo lineInfo = r as IJsonLineInfo;
691 JContainer parent = this;
695 if (parent is JProperty && ((JProperty)parent).Value != null)
700 parent = parent.Parent;
706 // new reader. move to actual content
708 case JsonToken.StartArray:
709 JArray a = new JArray();
710 a.SetLineInfo(lineInfo);
715 case JsonToken.EndArray:
719 parent = parent.Parent;
721 case JsonToken.StartObject:
722 JObject o = new JObject();
723 o.SetLineInfo(lineInfo);
727 case JsonToken.EndObject:
731 parent = parent.Parent;
733 case JsonToken.StartConstructor:
734 JConstructor constructor = new JConstructor(r.Value.ToString());
735 constructor.SetLineInfo(constructor);
736 parent.Add(constructor);
737 parent = constructor;
739 case JsonToken.EndConstructor:
743 parent = parent.Parent;
745 case JsonToken.String:
746 case JsonToken.Integer:
747 case JsonToken.Float:
749 case JsonToken.Boolean:
750 case JsonToken.Bytes:
751 JValue v = new JValue(r.Value);
752 v.SetLineInfo(lineInfo);
755 case JsonToken.Comment:
756 v = JValue.CreateComment(r.Value.ToString());
757 v.SetLineInfo(lineInfo);
761 v = new JValue(null, JTokenType.Null);
762 v.SetLineInfo(lineInfo);
765 case JsonToken.Undefined:
766 v = new JValue(null, JTokenType.Undefined);
767 v.SetLineInfo(lineInfo);
770 case JsonToken.PropertyName:
771 string propertyName = r.Value.ToString();
772 JProperty property = new JProperty(propertyName);
773 property.SetLineInfo(lineInfo);
774 JObject parentObject = (JObject) parent;
775 // handle multiple properties with the same name in JSON
776 JProperty existingPropertyWithName = parentObject.Property(propertyName);
777 if (existingPropertyWithName == null)
778 parent.Add(property);
780 existingPropertyWithName.Replace(property);
784 throw new InvalidOperationException("The JsonReader should not be on a token of type {0}.".FormatWith(CultureInfo.InvariantCulture, r.TokenType));
790 internal int ContentsHashCode()
793 foreach (JToken item in Children())
795 hashCode ^= item.GetDeepHashCode();
801 string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
806 PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
808 ICustomTypeDescriptor d = First as ICustomTypeDescriptor;
810 return d.GetProperties();
816 #region IList<JToken> Members
818 int IList<JToken>.IndexOf(JToken item)
820 return IndexOfItem(item);
823 void IList<JToken>.Insert(int index, JToken item)
825 InsertItem(index, item);
828 void IList<JToken>.RemoveAt(int index)
833 JToken IList<JToken>.this[int index]
835 get { return GetItem(index); }
836 set { SetItem(index, value); }
841 #region ICollection<JToken> Members
843 void ICollection<JToken>.Add(JToken item)
848 void ICollection<JToken>.Clear()
853 bool ICollection<JToken>.Contains(JToken item)
855 return ContainsItem(item);
858 void ICollection<JToken>.CopyTo(JToken[] array, int arrayIndex)
860 CopyItemsTo(array, arrayIndex);
863 int ICollection<JToken>.Count
865 get { return CountItems(); }
868 bool ICollection<JToken>.IsReadOnly
870 get { return false; }
873 bool ICollection<JToken>.Remove(JToken item)
875 return RemoveItem(item);
880 private JToken EnsureValue(object value)
886 return (JToken) value;
888 throw new ArgumentException("Argument is not a JToken.");
891 #region IList Members
893 int IList.Add(object value)
895 Add(EnsureValue(value));
896 return CountItems() - 1;
904 bool IList.Contains(object value)
906 return ContainsItem(EnsureValue(value));
909 int IList.IndexOf(object value)
911 return IndexOfItem(EnsureValue(value));
914 void IList.Insert(int index, object value)
916 InsertItem(index, EnsureValue(value));
919 bool IList.IsFixedSize
921 get { return false; }
924 bool IList.IsReadOnly
926 get { return false; }
929 void IList.Remove(object value)
931 RemoveItem(EnsureValue(value));
934 void IList.RemoveAt(int index)
939 object IList.this[int index]
941 get { return GetItem(index); }
942 set { SetItem(index, EnsureValue(value)); }
947 #region ICollection Members
949 void ICollection.CopyTo(Array array, int index)
951 CopyItemsTo(array, index);
954 int ICollection.Count
956 get { return CountItems(); }
959 bool ICollection.IsSynchronized
961 get { return false; }
964 object ICollection.SyncRoot
968 if (_syncRoot == null)
969 Interlocked.CompareExchange(ref _syncRoot, new object(), null);
978 #region IBindingList Members
981 void IBindingList.AddIndex(PropertyDescriptor property)
985 object IBindingList.AddNew()
987 AddingNewEventArgs args = new AddingNewEventArgs();
990 if (args.NewObject == null)
991 throw new Exception("Could not determine new value to add to '{0}'.".FormatWith(CultureInfo.InvariantCulture, GetType()));
993 if (!(args.NewObject is JToken))
994 throw new Exception("New item to be added to collection must be compatible with {0}.".FormatWith(CultureInfo.InvariantCulture, typeof (JToken)));
996 JToken newItem = (JToken)args.NewObject;
1002 bool IBindingList.AllowEdit
1004 get { return true; }
1007 bool IBindingList.AllowNew
1009 get { return true; }
1012 bool IBindingList.AllowRemove
1014 get { return true; }
1017 void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
1019 throw new NotSupportedException();
1022 int IBindingList.Find(PropertyDescriptor property, object key)
1024 throw new NotSupportedException();
1027 bool IBindingList.IsSorted
1029 get { return false; }
1032 void IBindingList.RemoveIndex(PropertyDescriptor property)
1036 void IBindingList.RemoveSort()
1038 throw new NotSupportedException();
1041 ListSortDirection IBindingList.SortDirection
1043 get { return ListSortDirection.Ascending; }
1046 PropertyDescriptor IBindingList.SortProperty
1048 get { return null; }
1051 bool IBindingList.SupportsChangeNotification
1053 get { return true; }
1056 bool IBindingList.SupportsSearching
1058 get { return false; }
1061 bool IBindingList.SupportsSorting
1063 get { return false; }