Skip to content

Commit eb5c1c3

Browse files
committed
Introduced a new ReadOnlyTopicMultiMap
The `ReadOnlyTopicMultiMap` operates similar to the new `TopicMultiMap`—and, indeed, requires a `TopicMultiMap` object as a source object—but only exposes a read only interface using an `IEnumerable<KeyValuesPair<string, ReadOnlyCollection<Topic>>>`. This ensures it doesn't have any confusing or unnecessary interface elements associated with e.g. `IDictionary` or `ICollection`, while still allowing users to enumerate and query the collection similar to an actual `TopicMultiMap`. The main difference is that the methods return a `ReadOnlyCollection<Topic>` or `IEnumerable<ReadOnlyCollection<Topic>>` instead of a `Collection<Topic>` or `Collection<KeyValuesPair<string, Collection<Topic>>`, as the `TopicMultiMap` does. This is intended to act as the base class for the underlying type of `Topic.Relationships`, as we want that to offer the semantics of a collection, while forcing users to update relationships using a controller interface in order to ensure we can enforce business logic such as tracking state and handling recipricol relationships. Note: This doesn't implement `IDictionary<string, ReadOnlyCollection<Topic>>`, even though it implements several related interface members, as that would force it to return an `IEnumerator<KeyValuePair<string, ReadOnlyCollection<Topic>>`, which introduces confusing semantics for a multi-map (e.g., `collection.Value` vs. `collection.Values`).
1 parent d049ea9 commit eb5c1c3

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
using System;
7+
using System.Collections;
8+
using System.Collections.Generic;
9+
using System.Collections.ObjectModel;
10+
using System.Diagnostics.CodeAnalysis;
11+
using System.Linq;
12+
using OnTopic.Internal.Diagnostics;
13+
14+
#pragma warning disable CA1710 // Identifiers should have correct suffix
15+
16+
namespace OnTopic.Collections {
17+
18+
/*============================================================================================================================
19+
| CLASS: READ-ONLY TOPIC MULTIMAP
20+
\---------------------------------------------------------------------------------------------------------------------------*/
21+
/// <summary>
22+
/// The <see cref="ReadOnlyTopicMultiMap"/> provides a read-only façade to a <see cref="TopicMultiMap"/>.
23+
/// </summary>
24+
public class ReadOnlyTopicMultiMap: IEnumerable<KeyValuesPair<string, ReadOnlyCollection<Topic>>> {
25+
26+
/*==========================================================================================================================
27+
| CONSTRUCTOR
28+
\-------------------------------------------------------------------------------------------------------------------------*/
29+
/// <summary>
30+
/// Constructs a new instance of a <see cref="ReadOnlyTopicMultiMap"/> class with a reference to an underlying <see cref=
31+
/// "TopicMultiMap"/> instance.
32+
/// </summary>
33+
public ReadOnlyTopicMultiMap(TopicMultiMap source) {
34+
Contract.Requires(source, nameof(source));
35+
Source = source;
36+
}
37+
38+
/// <summary>
39+
/// Constructs a new instance of a <see cref="ReadOnlyTopicMultiMap"/> class.
40+
/// </summary>
41+
/// <remarks>
42+
/// The <see cref="ReadOnlyTopicMultiMap"/> requires an underlying <see cref="Source"/> <see cref="TopicMultiMap"/> to
43+
/// derive values from. It's normally expected that callers will pass that via the public <see cref="
44+
/// ReadOnlyTopicMultiMap(TopicMultiMap)"/> constructor. Derived classes, however, cannot pass instance parameters to a
45+
/// base class. As such, the protected <see cref="ReadOnlyTopicMultiMap()"/> constructor allows the derived class to
46+
/// intialize the <see cref="ReadOnlyTopicMultiMap"/> without a <see cref="Source"/>—but expects that it will immediately
47+
/// set one via its constructor.
48+
/// </remarks>
49+
protected ReadOnlyTopicMultiMap() {}
50+
51+
/*==========================================================================================================================
52+
| PROPERTY: SOURCE
53+
\-------------------------------------------------------------------------------------------------------------------------*/
54+
/// <summary>
55+
/// Provides access to the underlying <see cref="TopicMultiMap"/> from which the <see cref="ReadOnlyTopicMultiMap"/> will
56+
/// derive values.
57+
/// </summary>
58+
/// <returns>
59+
/// The <see cref="Source"/> must be passed in via either the public <see cref="ReadOnlyTopicMultiMap(TopicMultiMap)"/>
60+
/// constructor, or must be set manually from the constructor of a derived class when using the protected <see cref="
61+
/// ReadOnlyTopicMultiMap()"/> constructor.
62+
/// </returns>
63+
[NotNull, DisallowNull]
64+
protected TopicMultiMap? Source { get; init; }
65+
66+
/*==========================================================================================================================
67+
| PROPERTY: KEYS
68+
\-------------------------------------------------------------------------------------------------------------------------*/
69+
/// <summary>
70+
/// Retrieves a list of keys available for the available collections.
71+
/// </summary>
72+
/// <returns>
73+
/// Returns an enumerable list of keys.
74+
/// </returns>
75+
public IEnumerable<string> Keys => Source.Select(m => m.Key).ToList();
76+
77+
/*==========================================================================================================================
78+
| PROPERTY: VALUES
79+
\-------------------------------------------------------------------------------------------------------------------------*/
80+
/// <summary>
81+
/// Retrieves a list of values available for the available collections.
82+
/// </summary>
83+
/// <returns>
84+
/// Returns an enumerable list of <see cref="ReadOnlyCollection{Topic}"/> instances.
85+
/// </returns>
86+
public IEnumerable<ReadOnlyCollection<Topic>> Values => Source.Select(m => new ReadOnlyCollection<Topic>(m.Values));
87+
88+
/*==========================================================================================================================
89+
| PROPERTY: COUNT
90+
\-------------------------------------------------------------------------------------------------------------------------*/
91+
/// <summary>
92+
/// Retrieves a count of items in the source collection.
93+
/// </summary>
94+
/// <returns>
95+
/// The number of collections in the underlying source collection.
96+
/// </returns>
97+
public int Count => Source.Count;
98+
99+
/*==========================================================================================================================
100+
| INDEXER
101+
\-------------------------------------------------------------------------------------------------------------------------*/
102+
/// <summary>
103+
/// Retrieves a <see cref="ReadOnlyCollection{Topic}"/> collection from the source collection based on the <paramref
104+
/// name="key"/>.
105+
/// </summary>
106+
/// <returns>
107+
/// A <see cref="ReadOnlyCollection{Topic}"/> collection.
108+
/// </returns>
109+
public ReadOnlyCollection<Topic> this[string key] => new(Source[key].Values);
110+
111+
/*==========================================================================================================================
112+
| METHOD: CONTAINS?
113+
\-------------------------------------------------------------------------------------------------------------------------*/
114+
/// <inheritdoc cref="KeyedCollection{TKey, TItem}.Contains(TKey)" />
115+
public bool Contains(string key) => Source.Contains(key);
116+
117+
/// <inheritdoc cref="TopicMultiMap.Contains(String, Topic)" />
118+
public bool Contains(string key, Topic topic) => Source.Contains(key, topic);
119+
120+
/*==========================================================================================================================
121+
| METHOD: GET TOPICS
122+
\-------------------------------------------------------------------------------------------------------------------------*/
123+
/// <summary>
124+
/// Retrieves a list of <see cref="Topic"/> objects grouped by a specific <paramref name="key"/>.
125+
/// </summary>
126+
/// <remarks>
127+
/// Returns a reference to the underlying <see cref="Collection{Topic}"/> collection.
128+
/// </remarks>
129+
/// <param name="key">The key of the collection to be returned.</param>
130+
public ReadOnlyCollection<Topic> GetTopics(string key) {
131+
Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(key), nameof(key));
132+
if (Contains(key)) {
133+
return new(Source[key].Values);
134+
}
135+
return new(new List<Topic>());
136+
}
137+
138+
/*==========================================================================================================================
139+
| METHOD: GET ALL TOPICS
140+
\-------------------------------------------------------------------------------------------------------------------------*/
141+
/// <summary>
142+
/// Retrieves a list of all related <see cref="Topic"/> objects, independent of collection key.
143+
/// </summary>
144+
/// <returns>
145+
/// Returns an enumerable list of <see cref="Topic"/> objects.
146+
/// </returns>
147+
public ReadOnlyCollection<Topic> GetAllTopics() =>
148+
Source.SelectMany(list => list.Values).Distinct().ToList().AsReadOnly();
149+
150+
/// <summary>
151+
/// Retrieves a list of all related <see cref="Topic"/> objects, independent of relationship key, filtered by content
152+
/// type.
153+
/// </summary>
154+
/// <returns>
155+
/// Returns an enumerable list of <see cref="Topic"/> objects.
156+
/// </returns>
157+
public ReadOnlyCollection<Topic> GetAllTopics(string contentType) =>
158+
GetAllTopics().Where(t => t.ContentType == contentType).ToList().AsReadOnly();
159+
160+
/*==========================================================================================================================
161+
| GET ENUMERATOR
162+
\-------------------------------------------------------------------------------------------------------------------------*/
163+
/// <inheritdoc/>
164+
public IEnumerator<KeyValuesPair<string, ReadOnlyCollection<Topic>>> GetEnumerator() {
165+
foreach (var collection in Source) {
166+
yield return new(collection.Key, new(collection.Values));
167+
}
168+
}
169+
170+
/// <inheritdoc/>
171+
IEnumerator IEnumerable.GetEnumerator() => Source.GetEnumerator();
172+
173+
} //Class
174+
} //Namespace
175+
176+
#pragma warning restore CA1710 // Identifiers should have correct suffix

0 commit comments

Comments
 (0)