private void AssertStringFieldIsMerged(ProgramInformationMergeFieldsFlags field, Action <TestProgramInformation> otherSourceFieldSetter, Func <IProgramInformation, string> informationFieldGetter) { var emptyInformation = new TestProgramInformation(); Assert.Null(informationFieldGetter(emptyInformation)); var otherInformationSource = new TestProgramInformation(); otherSourceFieldSetter(otherInformationSource); Assert.NotNull(informationFieldGetter(otherInformationSource)); var mergedInformation = emptyInformation.Merge(field, new Tuple <IProgramInformation, ProgramInformationMergeFieldsFlags>(otherInformationSource, field)); var expectedValue = informationFieldGetter(otherInformationSource); var actualValue = informationFieldGetter(mergedInformation); Assert.Equal(expectedValue, actualValue); }
/// <summary> /// Merges the given IProgramInformation data to form a new, combined version of the information. /// </summary> /// <param name="programInformation">The "primary" source of information.</param> /// <param name="fieldsToMerge">Identifies which fields to merge.</param> /// <param name="otherSources">The other information sources to merge, and how to merge them. See Remarks.</param> /// <returns>The merged program information.</returns> /// <remarks>Each of the additional information sources in the merge will be combined with the primary. The newly /// merged information is initialized using the data from <paramref name="programInformation"/>. The /// other sources should be ordered such that the first entry is the "most important" and the final entry the /// "least important". Each entry also describes which fields it is allowed to override in the merge. /// This is to offer a means of conflict resolution in situations arising from multiple sources containing /// the same information and attempting to override the default. For example, consider the case in which /// <paramref name="programInformation"/> and two entries are provided via <paramref name="otherSources"/>, /// all three of which define a value for <see cref="IProgramInformation.Title"/>. Here are some ways in which /// this can be configured: /// <code> /// fieldsToMerge = ProgramInformationMergeFieldsFlags.None; /// </code> /// In this case, the information in <paramref name="programInformation"/> will not be changed. A copy of the /// data within it will be returned. Note, however, that the specific implementation of <see cref="IProgramInformation"/> /// used to deliver the resulting data may be different than the original! /// Now, consider this scenario: /// <code> /// fieldsToMerge = ProgramInformationMergeFieldsFlags.Title; /// otherSources[0].Item2 = ProgramInformationMergeFieldsFlags.Title; /// otherSources[1].Item2 = ProgramInformationMergeFieldsFlags.Title; /// </code> /// In this case, each entry allows 'Title' to be set. Because <paramref name="programInformation"/> is /// treated as the highest priority, its value will be retained unless it is not set (in this case, a <c>null</c> /// or empty string). So, if programInformation.Title has no value, but otherSources[0].Item1.Title does, then /// the final result contains otherSources[0].Item1.Title, regardless of the value of otherSources[1].Item1.Title. /// By carefully considering these flags, you can have a reasonably rich order-of-precedence defined for establishing /// the data in the merged result. /// </remarks> /// <exception cref="System.ArgumentException">Thrown if unrecognized flags are provided in <paramref name="fieldsToMerge"/> or via <paramref name="otherSources"/>.</exception> public static IProgramInformation Merge(this IProgramInformation programInformation, ProgramInformationMergeFieldsFlags fieldsToMerge, params Tuple <IProgramInformation, ProgramInformationMergeFieldsFlags>[] otherSources) { // Grr.... PCLs (at least in .NET 4.0) don't support Enum.GetValues() var flagsToProcess = new[] { ProgramInformationMergeFieldsFlags.Title, ProgramInformationMergeFieldsFlags.Vendor, ProgramInformationMergeFieldsFlags.Year, ProgramInformationMergeFieldsFlags.Features, ProgramInformationMergeFieldsFlags.ShortName, ProgramInformationMergeFieldsFlags.Crcs }; var unknownFlagsForMerge = otherSources.Select(s => s.Item2).Concat(new[] { fieldsToMerge }).Except(flagsToProcess).Except(new[] { ProgramInformationMergeFieldsFlags.None, ProgramInformationMergeFieldsFlags.All }); if (unknownFlagsForMerge.Any()) { var unknownFlags = unknownFlagsForMerge.Aggregate((all, flag) => all | flag); throw new ArgumentException(string.Format(Resources.Strings.ProgramInformation_InvalidFieldFormat, unknownFlags)); } var mergedProgramInformation = new UserSpecifiedProgramInformation(programInformation); var mergedPriorityFlags = fieldsToMerge; // accumulates if a "higher priority" info has already claimed a field foreach (var otherSource in otherSources) { var otherInformation = otherSource.Item1; var updateFlags = otherSource.Item2; foreach (var flag in flagsToProcess) { if (updateFlags.HasFlag(flag)) { switch (flag) { case ProgramInformationMergeFieldsFlags.Title: if (string.IsNullOrEmpty(mergedProgramInformation.Title) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.Title = otherInformation.Title; if (!string.IsNullOrEmpty(mergedProgramInformation.Title)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Vendor: if (string.IsNullOrEmpty(mergedProgramInformation.Vendor) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.Vendor = otherInformation.Vendor; if (!string.IsNullOrEmpty(mergedProgramInformation.Vendor)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Year: if (string.IsNullOrEmpty(mergedProgramInformation.Year) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.Year = otherInformation.Year; if (!string.IsNullOrEmpty(mergedProgramInformation.Year)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Features: // This runs the risk of combining conflicting flags... mergedProgramInformation.Features = ProgramFeatures.Combine(mergedProgramInformation.Features, otherInformation.Features); mergedPriorityFlags |= flag; break; case ProgramInformationMergeFieldsFlags.ShortName: if (string.IsNullOrEmpty(mergedProgramInformation.ShortName) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.ShortName = otherInformation.ShortName; if (!string.IsNullOrEmpty(mergedProgramInformation.ShortName)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Crcs: var crcsToMerge = otherInformation.Crcs.Where(o => !mergedProgramInformation.Crcs.Any(m => m.Crc == o.Crc)); foreach (var crc in crcsToMerge) { mergedProgramInformation.AddCrc(crc.Crc, crc.Description, crc.Incompatibilities); mergedPriorityFlags |= flag; } break; } } } } return(mergedProgramInformation); }