Skip to content

Commit be1cb11

Browse files
author
Bart Koelman
committed
Refactored TypeHelper
1 parent 06210db commit be1cb11

39 files changed

+542
-551
lines changed

src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = fal
3131

3232
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary<Passport> resourcesByRelationship, ResourcePipeline pipeline)
3333
{
34-
resourcesByRelationship.GetByRelationship<Person>().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
34+
resourcesByRelationship.GetByRelationship<Person>().ToList().ForEach(pair => DisallowLocked(pair.Value));
3535
}
3636

3737
public override IEnumerable<Passport> OnReturn(HashSet<Passport> resources, ResourcePipeline pipeline)

src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public override IEnumerable<string> BeforeUpdateRelationship(HashSet<string> ids
2424

2525
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary<Person> resourcesByRelationship, ResourcePipeline pipeline)
2626
{
27-
resourcesByRelationship.GetByRelationship<Passport>().ToList().ForEach(kvp => DisallowLocked(kvp.Value));
27+
resourcesByRelationship.GetByRelationship<Passport>().ToList().ForEach(pair => DisallowLocked(pair.Value));
2828
}
2929
}
3030
}

src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = fal
3131

3232
public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary<TodoItem> resourcesByRelationship, ResourcePipeline pipeline)
3333
{
34-
List<TodoItem> todoItems = resourcesByRelationship.GetByRelationship<Person>().SelectMany(kvp => kvp.Value).ToList();
34+
List<TodoItem> todoItems = resourcesByRelationship.GetByRelationship<Person>().SelectMany(pair => pair.Value).ToList();
3535
DisallowLocked(todoItems);
3636
}
3737

src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace JsonApiDotNetCore.AtomicOperations.Processors
1414
public class SetRelationshipProcessor<TResource, TId> : ISetRelationshipProcessor<TResource, TId>
1515
where TResource : class, IIdentifiable<TId>
1616
{
17+
private readonly CollectionConverter _collectionConverter = new CollectionConverter();
1718
private readonly ISetRelationshipService<TResource, TId> _service;
1819

1920
public SetRelationshipProcessor(ISetRelationshipService<TResource, TId> service)
@@ -36,14 +37,14 @@ public virtual async Task<OperationContainer> ProcessAsync(OperationContainer op
3637
return null;
3738
}
3839

39-
private static object GetRelationshipRightValue(OperationContainer operation)
40+
private object GetRelationshipRightValue(OperationContainer operation)
4041
{
4142
RelationshipAttribute relationship = operation.Request.Relationship;
4243
object rightValue = relationship.GetValue(operation.Resource);
4344

4445
if (relationship is HasManyAttribute)
4546
{
46-
ICollection<IIdentifiable> rightResources = TypeHelper.ExtractResources(rightValue);
47+
ICollection<IIdentifiable> rightResources = _collectionConverter.ExtractResources(rightValue);
4748
return rightResources.ToHashSet(IdentifiableComparer.Instance);
4849
}
4950

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using JsonApiDotNetCore.Resources;
6+
7+
namespace JsonApiDotNetCore
8+
{
9+
internal class CollectionConverter
10+
{
11+
private static readonly Type[] HashSetCompatibleCollectionTypes =
12+
{
13+
typeof(HashSet<>),
14+
typeof(ICollection<>),
15+
typeof(ISet<>),
16+
typeof(IEnumerable<>),
17+
typeof(IReadOnlyCollection<>)
18+
};
19+
20+
/// <summary>
21+
/// Creates a collection instance based on the specified collection type and copies the specified elements into it.
22+
/// </summary>
23+
/// <param name="source">
24+
/// Source to copy from.
25+
/// </param>
26+
/// <param name="collectionType">
27+
/// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}).
28+
/// </param>
29+
public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType)
30+
{
31+
ArgumentGuard.NotNull(source, nameof(source));
32+
ArgumentGuard.NotNull(collectionType, nameof(collectionType));
33+
34+
Type concreteCollectionType = ToConcreteCollectionType(collectionType);
35+
dynamic concreteCollectionInstance = Activator.CreateInstance(concreteCollectionType);
36+
37+
foreach (object item in source)
38+
{
39+
concreteCollectionInstance!.Add((dynamic)item);
40+
}
41+
42+
return concreteCollectionInstance;
43+
}
44+
45+
/// <summary>
46+
/// Returns a compatible collection type that can be instantiated, for example IList{Article} -> List{Article} or ISet{Article} -> HashSet{Article}
47+
/// </summary>
48+
public Type ToConcreteCollectionType(Type collectionType)
49+
{
50+
if (collectionType.IsInterface && collectionType.IsGenericType)
51+
{
52+
Type genericTypeDefinition = collectionType.GetGenericTypeDefinition();
53+
54+
if (genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(ISet<>) ||
55+
genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(IReadOnlyCollection<>))
56+
{
57+
return typeof(HashSet<>).MakeGenericType(collectionType.GenericTypeArguments[0]);
58+
}
59+
60+
if (genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyList<>))
61+
{
62+
return typeof(List<>).MakeGenericType(collectionType.GenericTypeArguments[0]);
63+
}
64+
}
65+
66+
return collectionType;
67+
}
68+
69+
/// <summary>
70+
/// Returns a collection that contains zero, one or multiple resources, depending on the specified value.
71+
/// </summary>
72+
public ICollection<IIdentifiable> ExtractResources(object value)
73+
{
74+
if (value is ICollection<IIdentifiable> resourceCollection)
75+
{
76+
return resourceCollection;
77+
}
78+
79+
if (value is IEnumerable<IIdentifiable> resources)
80+
{
81+
return resources.ToList();
82+
}
83+
84+
if (value is IIdentifiable resource)
85+
{
86+
return resource.AsArray();
87+
}
88+
89+
return Array.Empty<IIdentifiable>();
90+
}
91+
92+
/// <summary>
93+
/// Returns the element type if the specified type is a generic collection, for example: IList{string} -> string or IList -> null.
94+
/// </summary>
95+
public Type TryGetCollectionElementType(Type type)
96+
{
97+
if (type != null)
98+
{
99+
if (type.IsGenericType && type.GenericTypeArguments.Length == 1)
100+
{
101+
if (type.IsOrImplementsInterface(typeof(IEnumerable)))
102+
{
103+
return type.GenericTypeArguments[0];
104+
}
105+
}
106+
}
107+
108+
return null;
109+
}
110+
111+
/// <summary>
112+
/// Indicates whether a <see cref="HashSet{T}" /> instance can be assigned to the specified type, for example IList{Article} -> false or ISet{Article} ->
113+
/// true.
114+
/// </summary>
115+
public bool TypeCanContainHashSet(Type collectionType)
116+
{
117+
if (collectionType.IsGenericType)
118+
{
119+
Type openCollectionType = collectionType.GetGenericTypeDefinition();
120+
return HashSetCompatibleCollectionTypes.Contains(openCollectionType);
121+
}
122+
123+
return false;
124+
}
125+
}
126+
}

src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu
108108
return this;
109109
}
110110

111-
if (TypeHelper.IsOrImplementsInterface(resourceType, typeof(IIdentifiable)))
111+
if (resourceType.IsOrImplementsInterface(typeof(IIdentifiable)))
112112
{
113113
string effectivePublicName = publicName ?? FormatResourceName(resourceType);
114114
Type effectiveIdType = idType ?? _typeLocator.TryGetIdType(resourceType);
@@ -288,7 +288,7 @@ private Type TryGetThroughType(PropertyInfo throughProperty)
288288
{
289289
Type constructedThroughType = typeof(ICollection<>).MakeGenericType(typeArguments[0]);
290290

291-
if (TypeHelper.IsOrImplementsInterface(throughProperty.PropertyType, constructedThroughType))
291+
if (throughProperty.PropertyType.IsOrImplementsInterface(constructedThroughType))
292292
{
293293
return typeArguments[0];
294294
}

src/JsonApiDotNetCore/Configuration/TypeLocator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public Type TryGetIdType(Type resourceType)
2727
/// </summary>
2828
public ResourceDescriptor TryGetResourceDescriptor(Type type)
2929
{
30-
if (TypeHelper.IsOrImplementsInterface(type, typeof(IIdentifiable)))
30+
if (type.IsOrImplementsInterface(typeof(IIdentifiable)))
3131
{
3232
Type idType = TryGetIdType(type);
3333

src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution
1515
public sealed class DiffableResourceHashSet<TResource> : ResourceHashSet<TResource>, IDiffableResourceHashSet<TResource>
1616
where TResource : class, IIdentifiable
1717
{
18+
// ReSharper disable once StaticMemberInGenericType
19+
private static readonly CollectionConverter CollectionConverter = new CollectionConverter();
20+
1821
private readonly HashSet<TResource> _databaseValues;
1922
private readonly bool _databaseValuesLoaded;
2023
private readonly IDictionary<PropertyInfo, HashSet<TResource>> _updatedAttributes;
@@ -59,15 +62,15 @@ public override HashSet<TResource> GetAffected(Expression<Func<TResource, object
5962
{
6063
ArgumentGuard.NotNull(navigationAction, nameof(navigationAction));
6164

62-
PropertyInfo propertyInfo = TypeHelper.ParseNavigationExpression(navigationAction);
65+
PropertyInfo propertyInfo = HooksNavigationParser.ParseNavigationExpression(navigationAction);
6366
Type propertyType = propertyInfo.PropertyType;
6467

65-
if (TypeHelper.IsOrImplementsInterface(propertyType, typeof(IEnumerable)))
68+
if (propertyType.IsOrImplementsInterface(typeof(IEnumerable)))
6669
{
67-
propertyType = TypeHelper.TryGetCollectionElementType(propertyType);
70+
propertyType = CollectionConverter.TryGetCollectionElementType(propertyType);
6871
}
6972

70-
if (TypeHelper.IsOrImplementsInterface(propertyType, typeof(IIdentifiable)))
73+
if (propertyType.IsOrImplementsInterface(typeof(IIdentifiable)))
7174
{
7275
// the navigation action references a relationship. Redirect the call to the relationship dictionary.
7376
return base.GetAffected(navigationAction);

src/JsonApiDotNetCore/Hooks/Internal/Execution/HookExecutorHelper.cs

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,22 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution
1919
/// <inheritdoc />
2020
internal sealed class HookExecutorHelper : IHookExecutorHelper
2121
{
22+
private static readonly HooksCollectionConverter CollectionConverter = new HooksCollectionConverter();
23+
private static readonly HooksObjectFactory ObjectFactory = new HooksObjectFactory();
2224
private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter();
2325

2426
private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance;
2527
private readonly IJsonApiOptions _options;
26-
private readonly IGenericServiceFactory _genericProcessorFactory;
28+
private readonly IGenericServiceFactory _genericServiceFactory;
2729
private readonly IResourceContextProvider _resourceContextProvider;
2830
private readonly Dictionary<RightType, IResourceHookContainer> _hookContainers;
2931
private readonly Dictionary<RightType, IHooksDiscovery> _hookDiscoveries;
3032
private readonly List<ResourceHook> _targetedHooksForRelatedResources;
3133

32-
public HookExecutorHelper(IGenericServiceFactory genericProcessorFactory, IResourceContextProvider resourceContextProvider, IJsonApiOptions options)
34+
public HookExecutorHelper(IGenericServiceFactory genericServiceFactory, IResourceContextProvider resourceContextProvider, IJsonApiOptions options)
3335
{
3436
_options = options;
35-
_genericProcessorFactory = genericProcessorFactory;
37+
_genericServiceFactory = genericServiceFactory;
3638
_resourceContextProvider = resourceContextProvider;
3739
_hookContainers = new Dictionary<RightType, IResourceHookContainer>();
3840
_hookDiscoveries = new Dictionary<RightType, IHooksDiscovery>();
@@ -48,7 +50,7 @@ public IResourceHookContainer GetResourceHookContainer(RightType targetResource,
4850
// so we need not even bother.
4951
if (!_hookContainers.TryGetValue(targetResource, out IResourceHookContainer container))
5052
{
51-
container = _genericProcessorFactory.Get<IResourceHookContainer>(typeof(ResourceHooksDefinition<>), targetResource);
53+
container = _genericServiceFactory.Get<IResourceHookContainer>(typeof(ResourceHooksDefinition<>), targetResource);
5254
_hookContainers[targetResource] = container;
5355
}
5456

@@ -91,23 +93,17 @@ public IResourceHookContainer<TResource> GetResourceHookContainer<TResource>(Res
9193

9294
public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, params RelationshipAttribute[] relationshipsToNextLayer)
9395
{
94-
LeftType idType = TypeHelper.GetIdType(resourceTypeForRepository);
96+
LeftType idType = ObjectFactory.GetIdType(resourceTypeForRepository);
9597

9698
MethodInfo parameterizedGetWhere =
9799
GetType().GetMethod(nameof(GetWhereWithInclude), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(resourceTypeForRepository,
98100
idType);
99101

100-
IEnumerable<IIdentifiable> cast = ((IEnumerable<object>)resources).Cast<IIdentifiable>();
101-
IList ids = TypeHelper.CopyToList(cast.Select(resource => resource.GetTypedId()), idType);
102-
var values = (IEnumerable)parameterizedGetWhere.Invoke(this, ArrayFactory.Create<object>(ids, relationshipsToNextLayer));
102+
IEnumerable<object> resourceIds = ((IEnumerable<object>)resources).Cast<IIdentifiable>().Select(resource => resource.GetTypedId());
103+
IList idsAsList = CollectionConverter.CopyToList(resourceIds, idType);
104+
var values = (IEnumerable)parameterizedGetWhere.Invoke(this, ArrayFactory.Create<object>(idsAsList, relationshipsToNextLayer));
103105

104-
if (values == null)
105-
{
106-
return null;
107-
}
108-
109-
return (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(resourceTypeForRepository),
110-
TypeHelper.CopyToList(values, resourceTypeForRepository));
106+
return values == null ? null : CollectionConverter.CopyToHashSet(values, resourceTypeForRepository);
111107
}
112108

113109
public bool ShouldLoadDbValues(LeftType resourceType, ResourceHook hook)
@@ -146,7 +142,7 @@ private IHooksDiscovery GetHookDiscovery(LeftType resourceType)
146142
{
147143
if (!_hookDiscoveries.TryGetValue(resourceType, out IHooksDiscovery discovery))
148144
{
149-
discovery = _genericProcessorFactory.Get<IHooksDiscovery>(typeof(IHooksDiscovery<>), resourceType);
145+
discovery = _genericServiceFactory.Get<IHooksDiscovery>(typeof(IHooksDiscovery<>), resourceType);
150146
_hookDiscoveries[resourceType] = discovery;
151147
}
152148

@@ -199,7 +195,7 @@ private static FilterExpression CreateFilterByIds<TId>(IReadOnlyCollection<TId>
199195
private IResourceReadRepository<TResource, TId> GetRepository<TResource, TId>()
200196
where TResource : class, IIdentifiable<TId>
201197
{
202-
return _genericProcessorFactory.Get<IResourceReadRepository<TResource, TId>>(typeof(IResourceReadRepository<,>), typeof(TResource), typeof(TId));
198+
return _genericServiceFactory.Get<IResourceReadRepository<TResource, TId>>(typeof(IResourceReadRepository<,>), typeof(TResource), typeof(TId));
203199
}
204200

205201
public IDictionary<RelationshipAttribute, IEnumerable> LoadImplicitlyAffected(IDictionary<RelationshipAttribute, IEnumerable> leftResourcesByRelation,
@@ -209,10 +205,10 @@ public IDictionary<RelationshipAttribute, IEnumerable> LoadImplicitlyAffected(ID
209205

210206
var implicitlyAffected = new Dictionary<RelationshipAttribute, IEnumerable>();
211207

212-
foreach (KeyValuePair<RelationshipAttribute, IEnumerable> kvp in leftResourcesByRelation)
208+
foreach (KeyValuePair<RelationshipAttribute, IEnumerable> pair in leftResourcesByRelation)
213209
{
214-
RelationshipAttribute relationship = kvp.Key;
215-
IEnumerable lefts = kvp.Value;
210+
RelationshipAttribute relationship = pair.Key;
211+
IEnumerable lefts = pair.Value;
216212

217213
if (relationship is HasManyThroughAttribute)
218214
{
@@ -225,45 +221,31 @@ public IDictionary<RelationshipAttribute, IEnumerable> LoadImplicitlyAffected(ID
225221
AddToImplicitlyAffected(includedLefts, relationship, existingRightResourceList, implicitlyAffected);
226222
}
227223

228-
return implicitlyAffected.ToDictionary(kvp => kvp.Key, kvp => TypeHelper.CreateHashSetFor(kvp.Key.RightType, kvp.Value));
224+
return implicitlyAffected.ToDictionary(pair => pair.Key, pair => CollectionConverter.CopyToHashSet(pair.Value, pair.Key.RightType));
229225
}
230226

231227
private void AddToImplicitlyAffected(IEnumerable includedLefts, RelationshipAttribute relationship, List<IIdentifiable> existingRightResourceList,
232228
Dictionary<RelationshipAttribute, IEnumerable> implicitlyAffected)
233229
{
234230
foreach (IIdentifiable ip in includedLefts)
235231
{
236-
IList dbRightResourceList = TypeHelper.CreateListFor(relationship.RightType);
237232
object relationshipValue = relationship.GetValue(ip);
238-
239-
if (!(relationshipValue is IEnumerable))
240-
{
241-
if (relationshipValue != null)
242-
{
243-
dbRightResourceList.Add(relationshipValue);
244-
}
245-
}
246-
else
247-
{
248-
AddToList(dbRightResourceList, (IEnumerable)relationshipValue);
249-
}
250-
251-
List<IIdentifiable> dbRightResourceListCast = dbRightResourceList.Cast<IIdentifiable>().ToList();
233+
ICollection<IIdentifiable> dbRightResources = CollectionConverter.ExtractResources(relationshipValue);
252234

253235
if (existingRightResourceList != null)
254236
{
255-
dbRightResourceListCast = dbRightResourceListCast.Except(existingRightResourceList, _comparer).ToList();
237+
dbRightResources = dbRightResources.Except(existingRightResourceList, _comparer).ToList();
256238
}
257239

258-
if (dbRightResourceListCast.Any())
240+
if (dbRightResources.Any())
259241
{
260242
if (!implicitlyAffected.TryGetValue(relationship, out IEnumerable affected))
261243
{
262-
affected = TypeHelper.CreateListFor(relationship.RightType);
244+
affected = CollectionConverter.CopyToList(Array.Empty<object>(), relationship.RightType);
263245
implicitlyAffected[relationship] = affected;
264246
}
265247

266-
AddToList((IList)affected, dbRightResourceListCast);
248+
AddToList((IList)affected, dbRightResources);
267249
}
268250
}
269251
}

0 commit comments

Comments
 (0)