chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//            Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
 
namespace OpenTap
{
    /// <summary> 
    /// Represents a stack of ITypeDataProvider/IStackedTypeDataProvider that is used to get TypeData for a given type. 
    /// The providers on this stack are called in order until a provider returuns a
    /// </summary>
    public class TypeDataProviderStack
    {
        object[] providers;
        int offset = 0;
 
        internal TypeDataProviderStack()
        {
            offset = 0;
            providers = GetProviders();
        }
 
        private TypeDataProviderStack(object[] providers, int providerOffset)
        {
            this.providers = providers;
            this.offset = providerOffset;
        }
 
        /// <summary> Gets the type data from an object. </summary>
        /// <param name="obj">The object to get type information for.</param>
        /// <returns>A representation of the type of the specified object or null if no providers can handle the specified type of object.</returns>
        public ITypeData GetTypeData(object obj)
        {
            if (obj == null)
                return null;
            while (offset < providers.Length)
            {
                var provider = providers[offset];
                offset++;
                try
                {
                    if (provider is IStackedTypeDataProvider sp)
                    {
                        var newStack = new TypeDataProviderStack(providers, offset);
                        if (sp.GetTypeData(obj, newStack) is ITypeData found)
                            return found;
                    }
                    else if (provider is ITypeDataProvider p)
                    {
                        if (p.GetTypeData(obj) is ITypeData found)
                            return found;
                    }
                }
                catch (Exception error)
                {
                    logProviderError(provider, error);
                }
            }
 
            if (!failedGetWarnHit)
            {
                // Todo: Change this method to never return null and throw in this case. Just added this warning for now, to see if this ever happens
                log.Warning("Could not get TypeData for {0}", obj.GetType().FullName);
                failedGetWarnHit = true;
            }
            return null;
        }
 
        static bool failedGetWarnHit = false;
        static TraceSource log = Log.CreateSource("TypeDataProvider");
 
        static void logProviderError(object provider, Exception error)
        {
            var log = Log.CreateSource(provider.GetType().Name);
            log.Error("Unhandled error occured in type resolution: {0}", error.Message);
            log.Debug(error);
        }
 
        /// <summary> Gets the type data from an identifier. </summary>
        /// <param name="identifier">The identifier to get type information for.</param>
        /// <returns>A representation of the type specified by identifier or null if no providers can handle the specified identifier.</returns>
        public ITypeData GetTypeData(string identifier)
        {
            if (identifier == null) return null;
            while (offset < providers.Length)
            {
                var provider = providers[offset];
                offset++;
                try
                {
                    if (provider is IStackedTypeDataProvider sp)
                    {
                        var newStack = new TypeDataProviderStack(providers, offset);
                        if (sp.GetTypeData(identifier, newStack) is ITypeData found)
                            return found;
                    }
                    else if (provider is ITypeDataProvider p)
                    {
                        if (p.GetTypeData(identifier) is ITypeData found)
                            return found;
                    }
                }
                catch (Exception error)
                {
                    logProviderError(provider, error);
                }
            }
 
            return null;
        }
 
        static object providersCacheLockObj = new object();
        static object[] providersCache = new object[0];
        static readonly HashSet<ITypeData> badProviders = new HashSet<ITypeData>();
        static int lastCount = 0;
        static object[] GetProviders()
        {
            var providers1 = TypeData.FromType(typeof(IStackedTypeDataProvider)).DerivedTypes;
            var providers2 = TypeData.FromType(typeof(ITypeDataProvider)).DerivedTypes;
 
            int l1 = providers1.Count();
            int l2 = providers2.Count();
 
            if (lastCount == l1 + l2) return providersCache;
 
            lock (providersCacheLockObj)
            {
                if (lastCount == l1 + l2) return providersCache;
                Dictionary<object, double> priorities = new Dictionary<object, double>();
                foreach (var providerType in providers1.Concat(providers2).Distinct())
                {
                    if (providerType.CanCreateInstance == false) continue;
 
                    try
                    {
                        var provider = providerType.CreateInstance();
                        if (provider == null)
                        {
                            throw new Exception(
                                $"Failed to instantiate TypeDataProvider of type '{providerType.Name}'.");
                        }
 
                        double priority;
 
                        if (provider is IStackedTypeDataProvider p)
                            priority = p.Priority;
                        else if (provider is ITypeDataProvider p2)
                            priority = p2.Priority;
                        else
                        {
                            lock (badProviders)
                            {
                                if (badProviders.Contains(providerType))
                                    continue; // error was printed first time, so just continue.
                            }
 
                            throw new InvalidOperationException("Unreachable code path executed.");
                        }
                        priorities.Add(provider, priority);
                    }
                    catch (Exception e)
                    {
                        while (e is TargetInvocationException te)
                            e = te.InnerException;
                        
                        bool isNewError;
                        lock (badProviders)
                            isNewError = badProviders.Add(providerType);
                        if (isNewError)
                        {
                            log.Error("Unable to use TypeDataProvider of type '{0}' due to errors.", providerType.Name);
                            log.Debug("The error was '{0}'", e.Message);
                            log.Debug(e);
                        }
                    }
                }
 
                providersCache = priorities.Keys.OrderByDescending(x => priorities[x]).ToArray();
                lastCount = l1 + l2;
            }
            return providersCache;
        }
    }
}