Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Actions / ExpandoClass.cs
1 /* ****************************************************************************
2  *
3  * Copyright (c) Microsoft Corporation. 
4  *
5  * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
6  * copy of the license can be found in the License.html file at the root of this distribution. If 
7  * you cannot locate the  Apache License, Version 2.0, please send an email to 
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
9  * by the terms of the Apache License, Version 2.0.
10  *
11  * You must not remove this notice, or any other, from this software.
12  *
13  *
14  * ***************************************************************************/
15
16 using System;
17 using System.Collections.Generic;
18 using System.Diagnostics;
19 using System.Dynamic.Utils;
20 using System.Text;
21
22 namespace System.Dynamic {
23     /// <summary>
24     /// Represents a dynamically assigned class.  Expando objects which share the same 
25     /// members will share the same class.  Classes are dynamically assigned as the
26     /// expando object gains members.
27     /// </summary>
28     internal class ExpandoClass {
29         private readonly string[] _keys;                            // list of names associated with each element in the data array, sorted
30         private readonly int _hashCode;                             // pre-calculated hash code of all the keys the class contains
31         private Dictionary<int, List<WeakReference>> _transitions;  // cached transitions
32
33         private const int EmptyHashCode = 6551;                     // hash code of the empty ExpandoClass.
34
35         internal static ExpandoClass Empty = new ExpandoClass();    // The empty Expando class - all Expando objects start off w/ this class.
36         
37         /// <summary>
38         /// Constructs the empty ExpandoClass.  This is the class used when an
39         /// empty Expando object is initially constructed.
40         /// </summary>
41         internal ExpandoClass() {
42             _hashCode = EmptyHashCode;
43             _keys = new string[0];
44         }
45
46         /// <summary>
47         /// Constructs a new ExpandoClass that can hold onto the specified keys.  The
48         /// keys must be sorted ordinally.  The hash code must be precalculated for 
49         /// the keys.
50         /// </summary>
51         internal ExpandoClass(string[] keys, int hashCode) {
52             _hashCode = hashCode;
53             _keys = keys;
54         }
55
56         /// <summary>
57         /// Finds or creates a new ExpandoClass given the existing set of keys
58         /// in this ExpandoClass plus the new key to be added. Members in an
59         /// ExpandoClass are always stored case sensitively.
60         /// </summary>
61         internal ExpandoClass FindNewClass(string newKey) {
62             // just XOR the newKey hash code 
63             int hashCode = _hashCode ^ newKey.GetHashCode();
64
65             lock (this) {
66                 List<WeakReference> infos = GetTransitionList(hashCode);
67
68                 for (int i = 0; i < infos.Count; i++) {
69                     ExpandoClass klass = infos[i].Target as ExpandoClass;
70                     if (klass == null) {
71                         infos.RemoveAt(i);
72                         i--;
73                         continue;
74                     }
75
76                     if (string.Equals(klass._keys[klass._keys.Length - 1], newKey, StringComparison.Ordinal)) {
77                         // the new key is the key we added in this transition
78                         return klass;
79                     }
80                 }
81
82                 // no applicable transition, create a new one
83                 string[] keys = new string[_keys.Length + 1];
84                 Array.Copy(_keys, keys, _keys.Length);
85                 keys[_keys.Length] = newKey;
86                 ExpandoClass ec = new ExpandoClass(keys, hashCode);
87
88                 infos.Add(new WeakReference(ec));
89                 return ec;
90             }
91         }
92
93         /// <summary>
94         /// Gets the lists of transitions that are valid from this ExpandoClass
95         /// to an ExpandoClass whos keys hash to the apporopriate hash code.
96         /// </summary>
97         private List<WeakReference> GetTransitionList(int hashCode) {
98             if (_transitions == null) {
99                 _transitions = new Dictionary<int, List<WeakReference>>();
100             }
101
102             List<WeakReference> infos;
103             if (!_transitions.TryGetValue(hashCode, out infos)) {
104                 _transitions[hashCode] = infos = new List<WeakReference>();
105             }
106
107             return infos;
108         }
109
110         /// <summary>
111         /// Gets the index at which the value should be stored for the specified name.
112         /// </summary>
113         internal int GetValueIndex(string name, bool caseInsensitive, ExpandoObject obj) {
114             if (caseInsensitive) {
115                 return GetValueIndexCaseInsensitive(name, obj);
116             } else {
117                 return GetValueIndexCaseSensitive(name);
118             }
119         }
120
121         /// <summary>
122         /// Gets the index at which the value should be stored for the specified name
123         /// case sensitively. Returns the index even if the member is marked as deleted.
124         /// </summary>
125         internal int GetValueIndexCaseSensitive(string name) {
126             for (int i = 0; i < _keys.Length; i++) {
127                 if (string.Equals(
128                     _keys[i],
129                     name,
130                     StringComparison.Ordinal)) {
131                     return i;
132                 }
133             }
134             return ExpandoObject.NoMatch;
135         }
136
137         /// <summary>
138         /// Gets the index at which the value should be stored for the specified name,
139         /// the method is only used in the case-insensitive case.
140         /// </summary>
141         /// <param name="name">the name of the member</param>
142         /// <param name="obj">The ExpandoObject associated with the class
143         /// that is used to check if a member has been deleted.</param>
144         /// <returns>
145         /// the exact match if there is one
146         /// if there is exactly one member with case insensitive match, return it
147         /// otherwise we throw AmbiguousMatchException.
148         /// </returns>
149         private int GetValueIndexCaseInsensitive(string name, ExpandoObject obj) {
150             int caseInsensitiveMatch = ExpandoObject.NoMatch; //the location of the case-insensitive matching member
151             lock (obj.LockObject) {
152                 for (int i = _keys.Length - 1; i >= 0; i--) {
153                     if (string.Equals(
154                         _keys[i],
155                         name,
156                         StringComparison.OrdinalIgnoreCase)) {
157                         //if the matching member is deleted, continue searching
158                         if (!obj.IsDeletedMember(i)) {
159                             if (caseInsensitiveMatch == ExpandoObject.NoMatch) {
160                                 caseInsensitiveMatch = i;
161                             } else {
162                                 //Ambigous match, stop searching
163                                 return ExpandoObject.AmbiguousMatchFound;
164                             }
165                         }
166                     }
167                 }
168             }
169             //There is exactly one member with case insensitive match.
170             return caseInsensitiveMatch;
171         }
172
173         /// <summary>
174         /// Gets the names of the keys that can be stored in the Expando class.  The
175         /// list is sorted ordinally.
176         /// </summary>
177         internal string[] Keys {
178             get {
179                 return _keys;
180             }
181         }
182     }
183 }