1
1
using System ;
2
2
using System . Collections ;
3
+ using System . Collections . Generic ;
4
+ using System . Linq ;
3
5
using System . Reflection ;
4
6
5
7
namespace NHibernate . Transform
@@ -29,37 +31,46 @@ namespace NHibernate.Transform
29
31
/// sorts them by inheritance depth then by visibility from public to private, and takes those ranking first.
30
32
/// </remarks>
31
33
[ Serializable ]
32
- public class AliasToBeanResultTransformer : AliasedTupleSubsetResultTransformer
34
+ public class AliasToBeanResultTransformer : AliasedTupleSubsetResultTransformer , IEquatable < AliasToBeanResultTransformer >
33
35
{
34
- private readonly QueryAliasToObjectPropertySetter _propertySetter ;
35
- private const BindingFlags flags = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance ;
36
- private readonly System . Type resultClass ;
37
- private readonly ConstructorInfo constructor ;
36
+ private readonly System . Type _resultClass ;
37
+ private readonly ConstructorInfo _beanConstructor ;
38
+ private readonly Dictionary < string , NamedMember < FieldInfo > > _fieldsByNameCaseSensitive ;
39
+ private readonly Dictionary < string , NamedMember < FieldInfo > > _fieldsByNameCaseInsensitive ;
40
+ private readonly Dictionary < string , NamedMember < PropertyInfo > > _propertiesByNameCaseSensitive ;
41
+ private readonly Dictionary < string , NamedMember < PropertyInfo > > _propertiesByNameCaseInsensitive ;
38
42
39
43
public AliasToBeanResultTransformer ( System . Type resultClass )
40
44
{
41
- this . resultClass = resultClass ?? throw new ArgumentNullException ( "resultClass" ) ;
45
+ _resultClass = resultClass ?? throw new ArgumentNullException ( "resultClass" ) ;
42
46
43
- constructor = resultClass . GetConstructor ( flags , null , System . Type . EmptyTypes , null ) ;
47
+ const BindingFlags bindingFlags = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance ;
48
+ _beanConstructor = resultClass . GetConstructor ( bindingFlags , null , System . Type . EmptyTypes , null ) ;
44
49
45
50
// if resultClass is a ValueType (struct), GetConstructor will return null...
46
51
// in that case, we'll use Activator.CreateInstance instead of the ConstructorInfo to create instances
47
- if ( constructor == null && resultClass . IsClass )
52
+ if ( _beanConstructor == null && resultClass . IsClass )
48
53
{
49
- throw new ArgumentException ( "The target class of a AliasToBeanResultTransformer need a parameter-less constructor" ,
50
- "resultClass" ) ;
54
+ throw new ArgumentException (
55
+ "The target class of a AliasToBeanResultTransformer need a parameter-less constructor" ,
56
+ nameof ( resultClass ) ) ;
51
57
}
52
58
53
- _propertySetter = QueryAliasToObjectPropertySetter . MakeFor ( resultClass ) ;
54
- }
59
+ var fields = new List < RankedMember < FieldInfo > > ( ) ;
60
+ var properties = new List < RankedMember < PropertyInfo > > ( ) ;
61
+ FetchFieldsAndProperties ( fields , properties ) ;
55
62
63
+ _fieldsByNameCaseSensitive = GetMapByName ( fields , StringComparer . Ordinal ) ;
64
+ _fieldsByNameCaseInsensitive = GetMapByName ( fields , StringComparer . OrdinalIgnoreCase ) ;
65
+ _propertiesByNameCaseSensitive = GetMapByName ( properties , StringComparer . Ordinal ) ;
66
+ _propertiesByNameCaseInsensitive = GetMapByName ( properties , StringComparer . OrdinalIgnoreCase ) ;
67
+ }
56
68
57
69
public override bool IsTransformedValueATupleElement ( String [ ] aliases , int tupleLength )
58
70
{
59
71
return false ;
60
72
}
61
73
62
-
63
74
public override object TransformTuple ( object [ ] tuple , String [ ] aliases )
64
75
{
65
76
if ( aliases == null )
@@ -70,22 +81,22 @@ public override object TransformTuple(object[] tuple, String[] aliases)
70
81
71
82
try
72
83
{
73
- result = resultClass . IsClass
74
- ? constructor . Invoke ( null )
75
- : Cfg . Environment . BytecodeProvider . ObjectsFactory . CreateInstance ( resultClass , true ) ;
84
+ result = _resultClass . IsClass
85
+ ? _beanConstructor . Invoke ( null )
86
+ : Cfg . Environment . BytecodeProvider . ObjectsFactory . CreateInstance ( _resultClass , true ) ;
76
87
77
88
for ( int i = 0 ; i < aliases . Length ; i ++ )
78
89
{
79
- _propertySetter . SetProperty ( aliases [ i ] , tuple [ i ] , result ) ;
90
+ SetProperty ( aliases [ i ] , tuple [ i ] , result ) ;
80
91
}
81
92
}
82
93
catch ( InstantiationException e )
83
94
{
84
- throw new HibernateException ( "Could not instantiate result class: " + resultClass . FullName , e ) ;
95
+ throw new HibernateException ( "Could not instantiate result class: " + _resultClass . FullName , e ) ;
85
96
}
86
97
catch ( MethodAccessException e )
87
98
{
88
- throw new HibernateException ( "Could not instantiate result class: " + resultClass . FullName , e ) ;
99
+ throw new HibernateException ( "Could not instantiate result class: " + _resultClass . FullName , e ) ;
89
100
}
90
101
91
102
return result ;
@@ -96,6 +107,173 @@ public override IList TransformList(IList collection)
96
107
return collection ;
97
108
}
98
109
110
+ #region Setter resolution
111
+
112
+ /// <summary>
113
+ /// Set the value of a property or field matching an alias.
114
+ /// </summary>
115
+ /// <param name="alias">The alias for which resolving the property or field.</param>
116
+ /// <param name="value">The value to which the property or field should be set.</param>
117
+ /// <param name="resultObj">The object on which to set the property or field. It must be of the type for which
118
+ /// this instance has been built.</param>
119
+ /// <exception cref="PropertyNotFoundException">Thrown if no matching property or field can be found.</exception>
120
+ /// <exception cref="AmbiguousMatchException">Thrown if many matching properties or fields are found, having the
121
+ /// same visibility and inheritance depth.</exception>
122
+ private void SetProperty ( string alias , object value , object resultObj )
123
+ {
124
+ if ( TrySet ( alias , value , resultObj , _propertiesByNameCaseSensitive ) )
125
+ return ;
126
+ if ( TrySet ( alias , value , resultObj , _fieldsByNameCaseSensitive ) )
127
+ return ;
128
+ if ( TrySet ( alias , value , resultObj , _propertiesByNameCaseInsensitive ) )
129
+ return ;
130
+ if ( TrySet ( alias , value , resultObj , _fieldsByNameCaseInsensitive ) )
131
+ return ;
132
+
133
+ throw new PropertyNotFoundException ( resultObj . GetType ( ) , alias , "setter" ) ;
134
+ }
135
+
136
+ private bool TrySet ( string alias , object value , object resultObj , Dictionary < string , NamedMember < FieldInfo > > fieldsMap )
137
+ {
138
+ if ( fieldsMap . TryGetValue ( alias , out var field ) )
139
+ {
140
+ CheckMember ( field , alias ) ;
141
+ field . Member . SetValue ( resultObj , value ) ;
142
+ return true ;
143
+ }
144
+ return false ;
145
+ }
146
+
147
+ private bool TrySet ( string alias , object value , object resultObj , Dictionary < string , NamedMember < PropertyInfo > > propertiesMap )
148
+ {
149
+ if ( propertiesMap . TryGetValue ( alias , out var property ) )
150
+ {
151
+ CheckMember ( property , alias ) ;
152
+ property . Member . SetValue ( resultObj , value ) ;
153
+ return true ;
154
+ }
155
+ return false ;
156
+ }
157
+
158
+ private void CheckMember < T > ( NamedMember < T > member , string alias ) where T : MemberInfo
159
+ {
160
+ if ( member . Member != null )
161
+ return ;
162
+
163
+ if ( member . AmbiguousMembers == null || member . AmbiguousMembers . Length < 2 )
164
+ {
165
+ // Should never happen, check NamedMember instanciations.
166
+ throw new InvalidOperationException (
167
+ $ "{ nameof ( NamedMember < T > . Member ) } missing and { nameof ( NamedMember < T > . AmbiguousMembers ) } invalid.") ;
168
+ }
169
+
170
+ throw new AmbiguousMatchException (
171
+ $ "Unable to find adequate property or field to set on '{ member . AmbiguousMembers [ 0 ] . DeclaringType . Name } ' for alias '{ alias } ', " +
172
+ $ "many { ( member . AmbiguousMembers [ 0 ] is PropertyInfo ? "properties" : "fields" ) } matches: " +
173
+ $ "{ string . Join ( ", " , member . AmbiguousMembers . Select ( m => m . Name ) ) } ") ;
174
+ }
175
+
176
+ private void FetchFieldsAndProperties ( List < RankedMember < FieldInfo > > fields , List < RankedMember < PropertyInfo > > properties )
177
+ {
178
+ const BindingFlags bindingFlags = BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ;
179
+ var currentType = _resultClass ;
180
+ var rank = 1 ;
181
+ // For grasping private members, we need to manually walk the hierarchy.
182
+ while ( currentType != null && currentType != typeof ( object ) )
183
+ {
184
+ fields . AddRange (
185
+ currentType
186
+ . GetFields ( bindingFlags )
187
+ . Select ( f => new RankedMember < FieldInfo > { Member = f , VisibilityRank = GetFieldVisibilityRank ( f ) , HierarchyRank = rank } ) ) ;
188
+ properties . AddRange (
189
+ currentType
190
+ . GetProperties ( bindingFlags )
191
+ . Where ( p => p . CanWrite && p . GetIndexParameters ( ) . Length == 0 )
192
+ . Select ( p => new RankedMember < PropertyInfo > { Member = p , VisibilityRank = GetPropertyVisibilityRank ( p ) , HierarchyRank = rank } ) ) ;
193
+ currentType = currentType . BaseType ;
194
+ rank ++ ;
195
+ }
196
+ }
197
+
198
+ private int GetFieldVisibilityRank ( FieldInfo field )
199
+ {
200
+ if ( field . IsPublic )
201
+ return 1 ;
202
+ if ( field . IsFamilyOrAssembly )
203
+ return 2 ;
204
+ if ( field . IsFamily )
205
+ return 3 ;
206
+ if ( field . IsPrivate )
207
+ return 4 ;
208
+ return 5 ;
209
+ }
210
+
211
+ private int GetPropertyVisibilityRank ( PropertyInfo property )
212
+ {
213
+ var setter = property . SetMethod ;
214
+ if ( setter . IsPublic )
215
+ return 1 ;
216
+ if ( setter . IsFamilyOrAssembly )
217
+ return 2 ;
218
+ if ( setter . IsFamily )
219
+ return 3 ;
220
+ if ( setter . IsPrivate )
221
+ return 4 ;
222
+ return 5 ;
223
+ }
224
+
225
+ private Dictionary < string , NamedMember < T > > GetMapByName < T > ( IEnumerable < RankedMember < T > > members , StringComparer comparer ) where T : MemberInfo
226
+ {
227
+ return members
228
+ . GroupBy ( m => m . Member . Name ,
229
+ ( k , g ) =>
230
+ new NamedMember < T > ( k ,
231
+ g
232
+ . GroupBy ( m => new { m . HierarchyRank , m . VisibilityRank } )
233
+ . OrderBy ( subg => subg . Key . HierarchyRank ) . ThenBy ( subg => subg . Key . VisibilityRank )
234
+ . First ( )
235
+ . Select ( m => m . Member )
236
+ . ToArray ( ) ) ,
237
+ comparer )
238
+ . ToDictionary ( f => f . Name , comparer ) ;
239
+ }
240
+
241
+ private struct RankedMember < T > where T : MemberInfo
242
+ {
243
+ public T Member ;
244
+ public int HierarchyRank ;
245
+ public int VisibilityRank ;
246
+ }
247
+
248
+ [ Serializable ]
249
+ private struct NamedMember < T > where T : MemberInfo
250
+ {
251
+ public NamedMember ( string name , T [ ] members )
252
+ {
253
+ if ( members == null )
254
+ throw new ArgumentNullException ( nameof ( members ) ) ;
255
+ Name = name ;
256
+ if ( members . Length == 1 )
257
+ {
258
+ Member = members [ 0 ] ;
259
+ AmbiguousMembers = null ;
260
+ }
261
+ else
262
+ {
263
+ Member = null ;
264
+ AmbiguousMembers = members ;
265
+ }
266
+ }
267
+
268
+ public string Name ;
269
+ public T Member ;
270
+ public T [ ] AmbiguousMembers ;
271
+ }
272
+
273
+ #endregion
274
+
275
+ #region Equality & hash-code
276
+
99
277
public override bool Equals ( object obj )
100
278
{
101
279
return Equals ( obj as AliasToBeanResultTransformer ) ;
@@ -111,12 +289,14 @@ public bool Equals(AliasToBeanResultTransformer other)
111
289
{
112
290
return true ;
113
291
}
114
- return Equals ( other . resultClass , resultClass ) ;
292
+ return Equals ( other . _resultClass , _resultClass ) ;
115
293
}
116
294
117
295
public override int GetHashCode ( )
118
296
{
119
- return resultClass . GetHashCode ( ) ;
297
+ return _resultClass . GetHashCode ( ) ;
120
298
}
299
+
300
+ #endregion
121
301
}
122
302
}
0 commit comments