5
5
6
6
namespace NHibernate . Transform
7
7
{
8
+ /// <summary>
9
+ /// Resolves setter for an alias with a heuristic: search among properties then fields for matching name and case, then,
10
+ /// if no matching property or field was found, retry with a case insensitive match. For members having the same name, it
11
+ /// sorts them by inheritance depth then by visibility from public to private, and takes those ranking first.
12
+ /// </summary>
8
13
[ Serializable ]
9
14
public class QueryAliasToObjectPropertySetter
10
15
{
11
- private readonly IEnumerable < FieldInfo > _fields ;
12
- private readonly IEnumerable < PropertyInfo > _properties ;
16
+ private readonly IDictionary < string , NamedMember < FieldInfo > > _fieldsByNameCaseSensitive ;
17
+ private readonly IDictionary < string , NamedMember < FieldInfo > > _fieldsByNameCaseInsensitive ;
18
+ private readonly IDictionary < string , NamedMember < PropertyInfo > > _propertiesByNameCaseSensitive ;
19
+ private readonly IDictionary < string , NamedMember < PropertyInfo > > _propertiesByNameCaseInsensitive ;
13
20
14
- private QueryAliasToObjectPropertySetter ( FieldInfo [ ] fields , PropertyInfo [ ] properties )
21
+ private QueryAliasToObjectPropertySetter ( IDictionary < string , NamedMember < FieldInfo > > fieldsByNameCaseSensitive ,
22
+ IDictionary < string , NamedMember < FieldInfo > > fieldsByNameCaseInsensitive ,
23
+ IDictionary < string , NamedMember < PropertyInfo > > propertiesByNameCaseSensitive ,
24
+ IDictionary < string , NamedMember < PropertyInfo > > propertiesByNameCaseInsensitive )
15
25
{
16
- _fields = fields ;
17
- _properties = properties ;
26
+ _fieldsByNameCaseSensitive = fieldsByNameCaseSensitive ;
27
+ _fieldsByNameCaseInsensitive = fieldsByNameCaseInsensitive ;
28
+ _propertiesByNameCaseSensitive = propertiesByNameCaseSensitive ;
29
+ _propertiesByNameCaseInsensitive = propertiesByNameCaseInsensitive ;
18
30
}
19
31
32
+ /// <summary>
33
+ /// Set the value of a property or field matching an alias.
34
+ /// </summary>
35
+ /// <param name="alias">The alias for which resolving the property or field.</param>
36
+ /// <param name="value">The value to which the property or field should be set.</param>
37
+ /// <param name="resultObj">The object on which to set the property or field. It must be of the type for which
38
+ /// this instance has been built.</param>
39
+ /// <exception cref="PropertyNotFoundException">Thrown if no matching property or field can be found.</exception>
40
+ /// <exception cref="AmbiguousMatchException">Thrown if many matching properties or fields are found, having the
41
+ /// same visibility and inheritance depth.</exception>
42
+ public void SetProperty ( string alias , object value , object resultObj )
43
+ {
44
+ if ( _propertiesByNameCaseSensitive . TryGetValue ( alias , out var property ) )
45
+ {
46
+ checkMember ( property ) ;
47
+ property . Member . SetValue ( resultObj , value ) ;
48
+ return ;
49
+ }
50
+ if ( _fieldsByNameCaseSensitive . TryGetValue ( alias , out var field ) )
51
+ {
52
+ checkMember ( field ) ;
53
+ field . Member . SetValue ( resultObj , value ) ;
54
+ return ;
55
+ }
56
+ if ( _propertiesByNameCaseInsensitive . TryGetValue ( alias , out property ) )
57
+ {
58
+ checkMember ( property ) ;
59
+ property . Member . SetValue ( resultObj , value ) ;
60
+ return ;
61
+ }
62
+ if ( _fieldsByNameCaseInsensitive . TryGetValue ( alias , out field ) )
63
+ {
64
+ checkMember ( field ) ;
65
+ field . Member . SetValue ( resultObj , value ) ;
66
+ return ;
67
+ }
68
+
69
+ throw new PropertyNotFoundException ( resultObj . GetType ( ) , alias , "setter" ) ;
70
+
71
+ void checkMember < T > ( NamedMember < T > member ) where T : MemberInfo
72
+ {
73
+ if ( member . Member != null )
74
+ return ;
75
+
76
+ if ( member . AmbiguousMembers == null || member . AmbiguousMembers . Length < 2 )
77
+ throw new InvalidOperationException ( $ "{ nameof ( NamedMember < T > . Member ) } missing and { nameof ( NamedMember < T > . AmbiguousMembers ) } invalid.") ;
78
+
79
+ throw new AmbiguousMatchException (
80
+ $ "Unable to find adequate property or field to set on '{ member . AmbiguousMembers [ 0 ] . DeclaringType . Name } ' for alias '{ alias } ', " +
81
+ $ "many { ( member . AmbiguousMembers [ 0 ] is PropertyInfo ? "properties" : "fields" ) } matches: { string . Join ( ", " , member . AmbiguousMembers . Select ( m => m . Name ) ) } ") ;
82
+ }
83
+ }
84
+
85
+ /// <summary>
86
+ /// Build a <c>QueryAliasToObjectPropertySetter</c> for a supplied type.
87
+ /// </summary>
88
+ /// <param name="objType">The type.</param>
89
+ /// <returns>A <c>QueryAliasToObjectPropertySetter</c>.</returns>
20
90
public static QueryAliasToObjectPropertySetter MakeFor ( System . Type objType )
21
91
{
22
- var bindingFlags = BindingFlags . Instance |
23
- BindingFlags . Public |
24
- BindingFlags . NonPublic |
25
- BindingFlags . IgnoreCase ;
26
- var fields = objType . GetFields ( bindingFlags ) ;
27
- var properties = objType . GetProperties ( bindingFlags ) ;
28
-
29
- return new QueryAliasToObjectPropertySetter ( fields , properties ) ;
92
+ var bindingFlags = BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ;
93
+ var fields = new List < RankedMember < FieldInfo > > ( ) ;
94
+ var properties = new List < RankedMember < PropertyInfo > > ( ) ;
95
+ var currentType = objType ;
96
+ var rank = 1 ;
97
+ // For grasping private members, we need to manually walk the hierarchy.
98
+ while ( currentType != null && currentType != typeof ( object ) )
99
+ {
100
+ fields . AddRange (
101
+ currentType
102
+ . GetFields ( bindingFlags )
103
+ . Select ( f => new RankedMember < FieldInfo > { Member = f , VisibilityRank = getFieldVisibilityRank ( f ) , HierarchyRank = rank } ) ) ;
104
+ properties . AddRange (
105
+ currentType
106
+ . GetProperties ( bindingFlags )
107
+ . Where ( p => p . CanWrite && p . GetIndexParameters ( ) . Length == 0 )
108
+ . Select ( p => new RankedMember < PropertyInfo > { Member = p , VisibilityRank = getPropertyVisibilityRank ( p ) , HierarchyRank = rank } ) ) ;
109
+ currentType = currentType . BaseType ;
110
+ rank ++ ;
111
+ }
112
+
113
+ var fieldsByNameCaseSensitive = getMapByName ( fields , StringComparer . Ordinal ) ;
114
+ var fieldsByNameCaseInsensitive = getMapByName ( fields , StringComparer . OrdinalIgnoreCase ) ;
115
+ var propertiesByNameCaseSensitive = getMapByName ( properties , StringComparer . Ordinal ) ;
116
+ var propertiesByNameCaseInsensitive = getMapByName ( properties , StringComparer . OrdinalIgnoreCase ) ;
117
+
118
+ return new QueryAliasToObjectPropertySetter ( fieldsByNameCaseSensitive , fieldsByNameCaseInsensitive , propertiesByNameCaseSensitive , propertiesByNameCaseInsensitive ) ;
119
+
120
+ int getFieldVisibilityRank ( FieldInfo field )
121
+ {
122
+ if ( field . IsPublic )
123
+ return 1 ;
124
+ if ( field . IsFamilyOrAssembly )
125
+ return 2 ;
126
+ if ( field . IsFamily )
127
+ return 3 ;
128
+ if ( field . IsPrivate )
129
+ return 4 ;
130
+ return 5 ;
131
+ }
132
+
133
+ int getPropertyVisibilityRank ( PropertyInfo property )
134
+ {
135
+ var setter = property . SetMethod ;
136
+ if ( setter . IsPublic )
137
+ return 1 ;
138
+ if ( setter . IsFamilyOrAssembly )
139
+ return 2 ;
140
+ if ( setter . IsFamily )
141
+ return 3 ;
142
+ if ( setter . IsPrivate )
143
+ return 4 ;
144
+ return 5 ;
145
+ }
146
+
147
+ Dictionary < string , NamedMember < T > > getMapByName < T > ( IEnumerable < RankedMember < T > > members , StringComparer comparer ) where T : MemberInfo
148
+ {
149
+ return members
150
+ . GroupBy ( m => m . Member . Name ,
151
+ ( k , g ) =>
152
+ new NamedMember < T > ( k ,
153
+ g
154
+ . GroupBy ( m => new { m . HierarchyRank , m . VisibilityRank } )
155
+ . OrderBy ( subg => subg . Key . HierarchyRank ) . ThenBy ( subg => subg . Key . VisibilityRank )
156
+ . First ( )
157
+ . Select ( m => m . Member )
158
+ . ToArray ( ) ) ,
159
+ comparer )
160
+ . ToDictionary ( f => f . Name , comparer ) ;
161
+ }
30
162
}
31
163
32
- public void SetProperty ( string alias , object value , object resultObj )
164
+ private struct RankedMember < T > where T : MemberInfo
33
165
{
34
- var property = _properties . SingleOrDefault ( prop => string . Equals ( prop . Name , alias , StringComparison . OrdinalIgnoreCase ) ) ;
35
- var field = _fields . SingleOrDefault ( prop => string . Equals ( prop . Name , alias , StringComparison . OrdinalIgnoreCase ) ) ;
36
- if ( field == null && property == null )
37
- throw new PropertyNotFoundException ( resultObj . GetType ( ) , alias , "setter" ) ;
166
+ public T Member ;
167
+ public int HierarchyRank ;
168
+ public int VisibilityRank ;
169
+ }
38
170
39
- if ( field != null )
171
+ [ Serializable ]
172
+ private struct NamedMember < T > where T : MemberInfo
173
+ {
174
+ public NamedMember ( string name , T [ ] members )
40
175
{
41
- field . SetValue ( resultObj , value ) ;
42
- return ;
176
+ if ( members == null )
177
+ throw new ArgumentNullException ( nameof ( members ) ) ;
178
+ Name = name ;
179
+ if ( members . Length == 1 )
180
+ {
181
+ Member = members [ 0 ] ;
182
+ AmbiguousMembers = null ;
183
+ }
184
+ else
185
+ {
186
+ Member = null ;
187
+ AmbiguousMembers = members ;
188
+ }
43
189
}
44
- if ( property != null && property . CanWrite )
45
- property . SetValue ( resultObj , value , new object [ 0 ] ) ;
190
+
191
+ public string Name ;
192
+ public T Member ;
193
+ public T [ ] AmbiguousMembers ;
46
194
}
47
195
}
48
- }
196
+ }
0 commit comments