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 , new object [ 0 ] ) ;
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 , new object [ 0 ] ) ;
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 )
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 ( MemberInfo member )
121
+ {
122
+ var field = ( FieldInfo ) member ;
123
+ if ( field . IsPublic )
124
+ return 1 ;
125
+ if ( field . IsFamilyOrAssembly )
126
+ return 2 ;
127
+ if ( field . IsFamily )
128
+ return 3 ;
129
+ if ( field . IsPrivate )
130
+ return 4 ;
131
+ return 5 ;
132
+ }
133
+
134
+ int getPropertyVisibilityRank ( MemberInfo member )
135
+ {
136
+ var setter = ( ( PropertyInfo ) member ) . SetMethod ;
137
+ if ( setter . IsPublic )
138
+ return 1 ;
139
+ if ( setter . IsFamilyOrAssembly )
140
+ return 2 ;
141
+ if ( setter . IsFamily )
142
+ return 3 ;
143
+ if ( setter . IsPrivate )
144
+ return 4 ;
145
+ return 5 ;
146
+ }
147
+
148
+ Dictionary < string , NamedMember < T > > getMapByName < T > ( IEnumerable < RankedMember < T > > members , StringComparer comparer ) where T : MemberInfo
149
+ {
150
+ return members
151
+ . GroupBy ( m => m . Member . Name ,
152
+ ( k , g ) =>
153
+ new NamedMember < T > ( k ,
154
+ g
155
+ . GroupBy ( m => new { m . HierarchyRank , m . VisibilityRank } )
156
+ . OrderBy ( subg => subg . Key . HierarchyRank ) . ThenBy ( subg => subg . Key . VisibilityRank )
157
+ . First ( )
158
+ . Select ( m => m . Member )
159
+ . ToArray ( ) ) ,
160
+ comparer )
161
+ . ToDictionary ( f => f . Name , comparer ) ;
162
+ }
30
163
}
31
164
32
- public void SetProperty ( string alias , object value , object resultObj )
165
+ private struct RankedMember < T > where T : MemberInfo
33
166
{
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" ) ;
167
+ public T Member ;
168
+ public int HierarchyRank ;
169
+ public int VisibilityRank ;
170
+ }
38
171
39
- if ( field != null )
172
+ [ Serializable ]
173
+ private struct NamedMember < T > where T : MemberInfo
174
+ {
175
+ public NamedMember ( string name , T [ ] members )
40
176
{
41
- field . SetValue ( resultObj , value ) ;
42
- return ;
177
+ if ( members == null )
178
+ throw new ArgumentNullException ( nameof ( members ) ) ;
179
+ Name = name ;
180
+ if ( members . Length == 1 )
181
+ {
182
+ Member = members [ 0 ] ;
183
+ AmbiguousMembers = null ;
184
+ }
185
+ else
186
+ {
187
+ Member = null ;
188
+ AmbiguousMembers = members ;
189
+ }
43
190
}
44
- if ( property != null && property . CanWrite )
45
- property . SetValue ( resultObj , value , new object [ 0 ] ) ;
191
+
192
+ public string Name ;
193
+ public T Member ;
194
+ public T [ ] AmbiguousMembers ;
46
195
}
47
196
}
48
- }
197
+ }
0 commit comments