From 33e5edf084e4adc729ee20e2d228868441982347 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 16:30:44 +0100 Subject: [PATCH 01/15] Add metrics tests that validate all the data - Add test to validate that the UsageService writes all the data - Add test to validate that the MetricsService sends all the data - Move the usage tracker tests into a decent namespace and one single fixture --- .../Services/UsageTrackerTests.cs | 393 ++++++++++-------- 1 file changed, 225 insertions(+), 168 deletions(-) diff --git a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs index f620ea74a3..e31c874d67 100644 --- a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs +++ b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.ObjectModel; using System.Reactive.Disposables; using System.Threading.Tasks; using GitHub.Extensions; @@ -8,209 +7,267 @@ using GitHub.Settings; using NSubstitute; using NUnit.Framework; +using System.Reflection; +using System.Linq; - -namespace UnitTests.GitHub.VisualStudio.Services +namespace MetricsTests { - public class UsageTrackerTests + public class UsageTrackerTests : TestBaseClass { - public class TheTimer : TestBaseClass + [Test] + public void ShouldStartTimer() { - [Test] - public void ShouldStartTimer() - { - var service = Substitute.For(); - var target = new UsageTracker(CreateServiceProvider(), service); + var service = Substitute.For(); + var target = new UsageTracker(CreateServiceProvider(), service); - service.Received(1).StartTimer(Arg.Any>(), TimeSpan.FromMinutes(3), TimeSpan.FromHours(8)); - } + service.Received(1).StartTimer(Arg.Any>(), TimeSpan.FromMinutes(3), TimeSpan.FromHours(8)); + } - [Test] - public async Task FirstTickShouldIncrementLaunchCount() - { - var service = CreateUsageService(); - var targetAndTick = CreateTargetAndGetTick(CreateServiceProvider(), service); + [Test] + public async Task FirstTickShouldIncrementLaunchCount() + { + var service = CreateUsageService(); + var targetAndTick = CreateTargetAndGetTick(CreateServiceProvider(), service); - await targetAndTick.Item2(); + await targetAndTick.Item2(); - await service.Received(1).WriteLocalData( - Arg.Is(x => - x.Model.NumberOfStartups == 1 && - x.Model.NumberOfStartupsWeek == 1 && - x.Model.NumberOfStartupsMonth == 1)); - } + await service.Received(1).WriteLocalData( + Arg.Is(x => + x.Model.NumberOfStartups == 1 && + x.Model.NumberOfStartupsWeek == 1 && + x.Model.NumberOfStartupsMonth == 1)); + } - [Test] - public async Task SubsequentTickShouldNotIncrementLaunchCount() - { - var service = CreateUsageService(); - var targetAndTick = CreateTargetAndGetTick(CreateServiceProvider(), service); + [Test] + public async Task SubsequentTickShouldNotIncrementLaunchCount() + { + var service = CreateUsageService(); + var targetAndTick = CreateTargetAndGetTick(CreateServiceProvider(), service); - await targetAndTick.Item2(); - service.ClearReceivedCalls(); - await targetAndTick.Item2(); + await targetAndTick.Item2(); + service.ClearReceivedCalls(); + await targetAndTick.Item2(); - await service.DidNotReceiveWithAnyArgs().WriteLocalData(null); - } + await service.DidNotReceiveWithAnyArgs().WriteLocalData(null); + } - [Test] - public async Task ShouldDisposeTimerIfMetricsServiceNotFound() - { - var service = CreateUsageService(); - var disposed = false; - var disposable = Disposable.Create(() => disposed = true); - service.StartTimer(null, new TimeSpan(), new TimeSpan()).ReturnsForAnyArgs(disposable); + [Test] + public async Task ShouldDisposeTimerIfMetricsServiceNotFound() + { + var service = CreateUsageService(); + var disposed = false; + var disposable = Disposable.Create(() => disposed = true); + service.StartTimer(null, new TimeSpan(), new TimeSpan()).ReturnsForAnyArgs(disposable); - var targetAndTick = CreateTargetAndGetTick( - CreateServiceProvider(hasMetricsService: false), - service); + var targetAndTick = CreateTargetAndGetTick( + CreateServiceProvider(hasMetricsService: false), + service); - await targetAndTick.Item2(); + await targetAndTick.Item2(); - Assert.True(disposed); - } + Assert.True(disposed); + } - [Test] - public async Task TickShouldNotSendDataIfSameDay() - { - var serviceProvider = CreateServiceProvider(); - var targetAndTick = CreateTargetAndGetTick( - serviceProvider, - CreateUsageService()); + [Test] + public async Task TickShouldNotSendDataIfSameDay() + { + var serviceProvider = CreateServiceProvider(); + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService()); - await targetAndTick.Item2(); + await targetAndTick.Item2(); - var metricsService = serviceProvider.TryGetService(); - await metricsService.DidNotReceive().PostUsage(Arg.Any()); - } + var metricsService = serviceProvider.TryGetService(); + await metricsService.DidNotReceive().PostUsage(Arg.Any()); + } - [Test] - public async Task TickShouldSendDataIfDifferentDay() - { - var serviceProvider = CreateServiceProvider(); - var targetAndTick = CreateTargetAndGetTick( - serviceProvider, - CreateUsageService(sameDay: false)); + [Test] + public async Task TickShouldSendDataIfDifferentDay() + { + var serviceProvider = CreateServiceProvider(); + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService(sameDay: false)); - await targetAndTick.Item2(); + await targetAndTick.Item2(); - var metricsService = serviceProvider.TryGetService(); - await metricsService.Received(1).PostUsage(Arg.Any()); - } + var metricsService = serviceProvider.TryGetService(); + await metricsService.Received(1).PostUsage(Arg.Any()); + } - [Test] - public async Task NonWeeklyOrMonthlyCountersShouldBeZeroed() + [Test] + public async Task NonWeeklyOrMonthlyCountersShouldBeZeroed() + { + var service = CreateUsageService(new UsageModel { - var service = CreateUsageService(new UsageModel - { - NumberOfStartups = 1, - NumberOfStartupsWeek = 1, - NumberOfStartupsMonth = 1, - NumberOfClones = 1, - }, sameDay: false); - Func tick = null; - - service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) - .Do(x => tick = x.ArgAt>(0)); - - var serviceProvider = CreateServiceProvider(); - var target = new UsageTracker(serviceProvider, service); - - await tick(); - - await service.Received().WriteLocalData( - Arg.Is(x => - x.Model.NumberOfStartups == 0 && - x.Model.NumberOfStartupsWeek == 2 && - x.Model.NumberOfStartupsMonth == 2 && - x.Model.NumberOfClones == 0)); - } + NumberOfStartups = 1, + NumberOfStartupsWeek = 1, + NumberOfStartupsMonth = 1, + NumberOfClones = 1, + }, sameDay: false); + Func tick = null; - [Test] - public async Task NonMonthlyCountersShouldBeZeroed() - { - var service = CreateUsageService(new UsageModel - { - NumberOfStartups = 1, - NumberOfStartupsWeek = 1, - NumberOfStartupsMonth = 1, - NumberOfClones = 1, - }, sameDay: false, sameWeek: false); - Func tick = null; - - service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) - .Do(x => tick = x.ArgAt>(0)); - - var serviceProvider = CreateServiceProvider(); - var target = new UsageTracker(serviceProvider, service); - - await tick(); - - await service.Received().WriteLocalData( - Arg.Is(x => - x.Model.NumberOfStartups == 0 && - x.Model.NumberOfStartupsWeek == 0 && - x.Model.NumberOfStartupsMonth == 2 && - x.Model.NumberOfClones == 0)); - } + service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) + .Do(x => tick = x.ArgAt>(0)); - [Test] - public async Task AllCountersShouldBeZeroed() - { - var service = CreateUsageService(new UsageModel - { - NumberOfStartups = 1, - NumberOfStartupsWeek = 1, - NumberOfStartupsMonth = 1, - NumberOfClones = 1, - }, sameDay: false, sameWeek: false, sameMonth: false); - Func tick = null; - - service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) - .Do(x => tick = x.ArgAt>(0)); - - var serviceProvider = CreateServiceProvider(); - var target = new UsageTracker(serviceProvider, service); - - await tick(); - - await service.Received().WriteLocalData( - Arg.Is(x => - x.Model.NumberOfStartups == 0 && - x.Model.NumberOfStartupsWeek == 0 && - x.Model.NumberOfStartupsMonth == 0 && - x.Model.NumberOfClones == 0)); - } + var serviceProvider = CreateServiceProvider(); + var target = new UsageTracker(serviceProvider, service); + + await tick(); + + await service.Received().WriteLocalData( + Arg.Is(x => + x.Model.NumberOfStartups == 0 && + x.Model.NumberOfStartupsWeek == 2 && + x.Model.NumberOfStartupsMonth == 2 && + x.Model.NumberOfClones == 0)); } - public class TheIncrementCounterMethod : TestBaseClass + [Test] + public async Task NonMonthlyCountersShouldBeZeroed() { - [Test] - public async Task ShouldIncrementCounter() + var service = CreateUsageService(new UsageModel { - var model = new UsageModel { NumberOfClones = 4 }; - var target = new UsageTracker( - CreateServiceProvider(), - CreateUsageService(model)); + NumberOfStartups = 1, + NumberOfStartupsWeek = 1, + NumberOfStartupsMonth = 1, + NumberOfClones = 1, + }, sameDay: false, sameWeek: false); + Func tick = null; + + service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) + .Do(x => tick = x.ArgAt>(0)); - await target.IncrementCounter(x => x.NumberOfClones); + var serviceProvider = CreateServiceProvider(); + var target = new UsageTracker(serviceProvider, service); - Assert.That(5, Is.EqualTo(model.NumberOfClones)); - } + await tick(); - [Test] - public async Task ShouldWriteUpdatedData() + await service.Received().WriteLocalData( + Arg.Is(x => + x.Model.NumberOfStartups == 0 && + x.Model.NumberOfStartupsWeek == 0 && + x.Model.NumberOfStartupsMonth == 2 && + x.Model.NumberOfClones == 0)); + } + + [Test] + public async Task AllCountersShouldBeZeroed() + { + var service = CreateUsageService(new UsageModel { - var data = new UsageData { Model = new UsageModel() }; - var service = CreateUsageService(data); - var target = new UsageTracker( - CreateServiceProvider(), - service); + NumberOfStartups = 1, + NumberOfStartupsWeek = 1, + NumberOfStartupsMonth = 1, + NumberOfClones = 1, + }, sameDay: false, sameWeek: false, sameMonth: false); + Func tick = null; + + service.WhenForAnyArgs(x => x.StartTimer(null, new TimeSpan(), new TimeSpan())) + .Do(x => tick = x.ArgAt>(0)); + + var serviceProvider = CreateServiceProvider(); + var target = new UsageTracker(serviceProvider, service); + + await tick(); + + await service.Received().WriteLocalData( + Arg.Is(x => + x.Model.NumberOfStartups == 0 && + x.Model.NumberOfStartupsWeek == 0 && + x.Model.NumberOfStartupsMonth == 0 && + x.Model.NumberOfClones == 0)); + } + + [Test] + public async Task ShouldIncrementCounter() + { + var model = new UsageModel { NumberOfClones = 4 }; + var target = new UsageTracker( + CreateServiceProvider(), + CreateUsageService(model)); + + await target.IncrementCounter(x => x.NumberOfClones); + + Assert.That(5, Is.EqualTo(model.NumberOfClones)); + } + + [Test] + public async Task ShouldWriteUpdatedData() + { + var data = new UsageData { Model = new UsageModel() }; + var service = CreateUsageService(data); + var target = new UsageTracker( + CreateServiceProvider(), + service); + + await target.IncrementCounter(x => x.NumberOfClones); + + await service.Received(1).WriteLocalData(data); + } + + [Test] + public async Task UsageServiceWritesAllTheDataCorrectly() + { + var model = CreateUsageModel(); + var serviceProvider = CreateServiceProvider(); + var usageService = CreateUsageService(model, sameDay: false); + var targetAndTick = CreateTargetAndGetTick(serviceProvider, usageService); - await target.IncrementCounter(x => x.NumberOfClones); + await targetAndTick.Item2(); + + var metricsService = serviceProvider.TryGetService(); + + UsageData result = usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData; + CollectionAssert.AreEquivalent( + model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), + result.Model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) })); + } + + [Test] + public async Task MetricserviceSendsAllTheDataCorrectly() + { + var model = CreateUsageModel(); + var serviceProvider = CreateServiceProvider(); + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService(model, sameDay: false)); - await service.Received(1).WriteLocalData(data); + await targetAndTick.Item2(); + + var metricsService = serviceProvider.TryGetService(); + + UsageData result = metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0] as UsageData; + CollectionAssert.AreEquivalent( + model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), + result.Model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) })); + } + + private static UsageModel CreateUsageModel() + { + var count = 1; + var model = new UsageModel(); + var properties = model.GetType().GetRuntimeProperties(); + foreach (var property in properties) + { + if (property.PropertyType == typeof(int)) + { + property.SetValue(model, count++); + } + else if (property.PropertyType == typeof(string)) + { + property.SetValue(model, $"string {count++}"); + } + else if (property.PropertyType == typeof(bool)) + { + property.SetValue(model, true); + } + else + Assert.Fail("Unknown field type in UsageModel. Fix this test to support it"); } + + return model; } static Tuple> CreateTargetAndGetTick( From 40b19cb057381c006e8dc432db18296d54650588 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 16:40:03 +0100 Subject: [PATCH 02/15] Ooops typo --- .../GitHub.VisualStudio/Services/UsageTrackerTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs index e31c874d67..6783b483ca 100644 --- a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs +++ b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs @@ -222,7 +222,7 @@ public async Task UsageServiceWritesAllTheDataCorrectly() UsageData result = usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData; CollectionAssert.AreEquivalent( model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), - result.Model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) })); + result.Model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(result.Model) })); } [Test] @@ -238,10 +238,11 @@ public async Task MetricserviceSendsAllTheDataCorrectly() var metricsService = serviceProvider.TryGetService(); - UsageData result = metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0] as UsageData; + var list = metricsService.ReceivedCalls().Select(x => x.GetMethodInfo().Name); + var result = metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0] as UsageModel; CollectionAssert.AreEquivalent( - model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), - result.Model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) })); + model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), + result.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(result) })); } private static UsageModel CreateUsageModel() From d28c8644935d6a887776979d77195f4c34c0877d Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 18:01:13 +0100 Subject: [PATCH 03/15] This really needs to be a struct --- src/GitHub.Exports/Models/UsageModel.cs | 107 ++++++++++++------------ 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs index 19f8cfb2fd..11c70ea7d8 100644 --- a/src/GitHub.Exports/Models/UsageModel.cs +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -3,64 +3,67 @@ namespace GitHub.Models { - public class UsageModel + public struct UsageModel { - public bool IsGitHubUser { get; set; } - public bool IsEnterpriseUser { get; set; } - public string AppVersion { get; set; } - public string VSVersion { get; set; } - public string Lang { get; set; } - public int NumberOfStartups { get; set; } - public int NumberOfStartupsWeek { get; set; } - public int NumberOfStartupsMonth { get; set; } - public int NumberOfUpstreamPullRequests { get; set; } - public int NumberOfClones { get; set; } - public int NumberOfReposCreated { get; set; } - public int NumberOfReposPublished { get; set; } - public int NumberOfGists { get; set; } - public int NumberOfOpenInGitHub { get; set; } - public int NumberOfLinkToGitHub { get; set; } - public int NumberOfLogins { get; set; } - public int NumberOfOAuthLogins { get; set; } - public int NumberOfTokenLogins { get; set; } - public int NumberOfPullRequestsOpened { get; set; } - public int NumberOfLocalPullRequestsCheckedOut { get; set; } - public int NumberOfLocalPullRequestPulls { get; set; } - public int NumberOfLocalPullRequestPushes { get; set; } - public int NumberOfForkPullRequestsCheckedOut { get; set; } - public int NumberOfForkPullRequestPulls { get; set; } - public int NumberOfForkPullRequestPushes { get; set; } - public int NumberOfWelcomeDocsClicks { get; set; } - public int NumberOfWelcomeTrainingClicks { get; set; } - public int NumberOfGitHubPaneHelpClicks { get; set; } - public int NumberOfPRDetailsViewChanges { get; set; } - public int NumberOfPRDetailsViewFile { get; set; } - public int NumberOfPRDetailsCompareWithSolution { get; set; } - public int NumberOfPRDetailsOpenFileInSolution { get; set; } - public int NumberOfPRReviewDiffViewInlineCommentOpen { get; set; } - public int NumberOfPRReviewDiffViewInlineCommentPost { get; set; } + public bool IsGitHubUser; + public bool IsEnterpriseUser; + public string AppVersion; + public string VSVersion; + public string Lang; + public int NumberOfStartups; + public int NumberOfStartupsWeek; + public int NumberOfStartupsMonth; + public int NumberOfUpstreamPullRequests; + public int NumberOfClones; + public int NumberOfReposCreated; + public int NumberOfReposPublished; + public int NumberOfGists; + public int NumberOfOpenInGitHub; + public int NumberOfLinkToGitHub; + public int NumberOfLogins; + public int NumberOfOAuthLogins; + public int NumberOfTokenLogins; + public int NumberOfPullRequestsOpened; + public int NumberOfLocalPullRequestsCheckedOut; + public int NumberOfLocalPullRequestPulls; + public int NumberOfLocalPullRequestPushes; + public int NumberOfForkPullRequestsCheckedOut; + public int NumberOfForkPullRequestPulls; + public int NumberOfForkPullRequestPushes; + public int NumberOfWelcomeDocsClicks; + public int NumberOfWelcomeTrainingClicks; + public int NumberOfGitHubPaneHelpClicks; + public int NumberOfPRDetailsViewChanges; + public int NumberOfPRDetailsViewFile; + public int NumberOfPRDetailsCompareWithSolution; + public int NumberOfPRDetailsOpenFileInSolution; + public int NumberOfPRDetailsNavigateToEditor; + public int NumberOfPRReviewDiffViewInlineCommentOpen; + public int NumberOfPRReviewDiffViewInlineCommentPost; public UsageModel Clone(bool includeWeekly, bool includeMonthly) { - var result = new UsageModel(); - var properties = result.GetType().GetRuntimeProperties(); - - foreach (var property in properties) - { - var cloneValue = property.PropertyType == typeof(int); - - if (property.Name == nameof(result.NumberOfStartupsWeek)) - cloneValue = includeWeekly; - else if (property.Name == nameof(result.NumberOfStartupsMonth)) - cloneValue = includeMonthly; + var result = this; + if (!includeWeekly) + result.NumberOfStartupsWeek = 0; + if (!includeMonthly) + result.NumberOfStartupsMonth = 0; + return result; + } - if (cloneValue) - { - var value = property.GetValue(this); - property.SetValue(result, value); - } - } + public UsageModel ClearCounters(bool clearWeekly, bool clearMonthly) + { + var result = new UsageModel(); + if (!clearWeekly) + result.NumberOfStartupsWeek = NumberOfStartupsWeek; + if (!clearMonthly) + result.NumberOfStartupsMonth = NumberOfStartupsMonth; + result.IsGitHubUser = IsGitHubUser; + result.IsEnterpriseUser = IsEnterpriseUser; + result.AppVersion = AppVersion; + result.VSVersion = VSVersion; + result.Lang = Lang; return result; } } From 67900f661126e960ddb85f495acc75b48f317dc0 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 18:02:26 +0100 Subject: [PATCH 04/15] It's handy to have the same assembly info on the tests --- test/UnitTests/Properties/AssemblyInfo.cs | 28 +---------------------- test/UnitTests/UnitTests.csproj | 5 +++- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/test/UnitTests/Properties/AssemblyInfo.cs b/test/UnitTests/Properties/AssemblyInfo.cs index cf50347845..23b6353232 100644 --- a/test/UnitTests/Properties/AssemblyInfo.cs +++ b/test/UnitTests/Properties/AssemblyInfo.cs @@ -8,32 +8,6 @@ // associated with an assembly. [assembly: AssemblyTitle("UnitTests")] [assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UnitTests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7c0cb7e5-6c7b-4f11-8454-9e1a4747641c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -//[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] \ No newline at end of file +[assembly: Guid("7c0cb7e5-6c7b-4f11-8454-9e1a4747641c")] \ No newline at end of file diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index d2a8578c43..bd5282d9e4 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -285,7 +285,10 @@ - + + Properties\SolutionInfo.cs + + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203} From 23cd8707cbc7899e15dceed8a9fc4f144e279f56 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 18:11:39 +0100 Subject: [PATCH 05/15] Fix metrics and tests for them --- .../Services/UsageTracker.cs | 72 +++++------ .../Services/UsageTrackerTests.cs | 120 +++++++++++++++--- test/UnitTests/UnitTests.csproj | 3 +- 3 files changed, 135 insertions(+), 60 deletions(-) diff --git a/src/GitHub.VisualStudio/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs index 4ee057db37..bd4220afe0 100644 --- a/src/GitHub.VisualStudio/Services/UsageTracker.cs +++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs @@ -44,10 +44,13 @@ public void Dispose() public async Task IncrementCounter(Expression> counter) { var usage = await LoadUsage(); - var property = (MemberExpression)counter.Body; - var propertyInfo = (PropertyInfo)property.Member; - var value = (int)propertyInfo.GetValue(usage.Model); - propertyInfo.SetValue(usage.Model, value + 1); + // because Model is a struct, it needs to be boxed in order for reflection to work + object model = usage.Model; + var field = (MemberExpression)counter.Body; + var fieldInfo = (FieldInfo)field.Member; + var value = (int)fieldInfo.GetValue(model); + fieldInfo.SetValue(model, value + 1); + usage.Model = (UsageModel)model; await service.WriteLocalData(usage); } @@ -75,9 +78,11 @@ async Task Initialize() async Task IncrementLaunchCount() { var usage = await LoadUsage(); - ++usage.Model.NumberOfStartups; - ++usage.Model.NumberOfStartupsWeek; - ++usage.Model.NumberOfStartupsMonth; + var model = usage.Model; + model.NumberOfStartups++; + model.NumberOfStartupsWeek++; + model.NumberOfStartupsMonth++; + usage.Model = model; await service.WriteLocalData(usage); } @@ -85,11 +90,13 @@ async Task LoadUsage() { await Initialize(); - var result = await service.ReadLocalData(); - result.Model.Lang = CultureInfo.InstalledUICulture.IetfLanguageTag; - result.Model.AppVersion = AssemblyVersionInformation.Version; - result.Model.VSVersion = vsservices.VSVersion; - return result; + var usage = await service.ReadLocalData(); + var model = usage.Model; + model.Lang = CultureInfo.InstalledUICulture.IetfLanguageTag; + model.AppVersion = AssemblyVersionInformation.Version; + model.VSVersion = vsservices.VSVersion; + usage.Model = model; + return usage; } async Task TimerTick() @@ -120,52 +127,39 @@ async Task TimerTick() // Only send stats once a day. if (!service.IsSameDay(usage.LastUpdated)) { + usage.Model = UpdateModelUserData(usage.Model); + await SendUsage(usage.Model, includeWeekly, includeMonthly); - ClearCounters(usage.Model, includeWeekly, includeMonthly); + + usage.Model = usage.Model.ClearCounters(includeWeekly, includeMonthly); usage.LastUpdated = DateTimeOffset.Now.UtcDateTime; await service.WriteLocalData(usage); } } - async Task SendUsage(UsageModel usage, bool weekly, bool monthly) + async Task SendUsage(UsageModel usage, bool includeWeekly, bool includeMonthly) { if (client == null) { throw new GitHubLogicException("SendUsage should not be called when there is no IMetricsService"); } - if (connectionManager.Connections.Any(x => x.HostAddress.IsGitHubDotCom())) - { - usage.IsGitHubUser = true; - } - - if (connectionManager.Connections.Any(x => !x.HostAddress.IsGitHubDotCom())) - { - usage.IsEnterpriseUser = true; - } - - var model = usage.Clone(weekly, monthly); + var model = usage.Clone(includeWeekly, includeMonthly); await client.PostUsage(model); } - static void ClearCounters(UsageModel usage, bool weekly, bool monthly) + UsageModel UpdateModelUserData(UsageModel model) { - var properties = usage.GetType().GetRuntimeProperties(); - - foreach (var property in properties) + if (connectionManager.Connections.Any(x => x.HostAddress.IsGitHubDotCom())) { - var setValue = property.PropertyType == typeof(int); - - if (property.Name == nameof(usage.NumberOfStartupsWeek)) - setValue = weekly; - else if (property.Name == nameof(usage.NumberOfStartupsMonth)) - setValue = monthly; + model.IsGitHubUser = true; + } - if (setValue) - { - property.SetValue(usage, 0); - } + if (connectionManager.Connections.Any(x => !x.HostAddress.IsGitHubDotCom())) + { + model.IsEnterpriseUser = true; } + return model; } } } diff --git a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs index 6783b483ca..fc9f5d02e2 100644 --- a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs +++ b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs @@ -9,6 +9,8 @@ using NUnit.Framework; using System.Reflection; using System.Linq; +using System.Globalization; +using GitHub.Reflection; namespace MetricsTests { @@ -184,13 +186,15 @@ await service.Received().WriteLocalData( public async Task ShouldIncrementCounter() { var model = new UsageModel { NumberOfClones = 4 }; + var usageService = CreateUsageService(model); var target = new UsageTracker( CreateServiceProvider(), - CreateUsageService(model)); + usageService); await target.IncrementCounter(x => x.NumberOfClones); + UsageData result = usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData; - Assert.That(5, Is.EqualTo(model.NumberOfClones)); + Assert.AreEqual(5, result.Model.NumberOfClones); } [Test] @@ -212,24 +216,34 @@ public async Task UsageServiceWritesAllTheDataCorrectly() { var model = CreateUsageModel(); var serviceProvider = CreateServiceProvider(); - var usageService = CreateUsageService(model, sameDay: false); + var usageService = CreateUsageService(model, sameDay: true); var targetAndTick = CreateTargetAndGetTick(serviceProvider, usageService); + var vsservices = serviceProvider.GetService(); + vsservices.VSVersion.Returns(model.VSVersion); await targetAndTick.Item2(); var metricsService = serviceProvider.TryGetService(); + var expected = model; + expected.NumberOfStartups++; + expected.NumberOfStartupsWeek++; + expected.NumberOfStartupsMonth++; + UsageData result = usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData; CollectionAssert.AreEquivalent( - model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), - result.Model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(result.Model) })); + ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetFields(result.Model.GetType()).Select(x => new { x.Name, Value = x.GetValue(result.Model) })); } - [Test] - public async Task MetricserviceSendsAllTheDataCorrectly() + [Test] + public async Task MetricserviceSendsDailyData() { var model = CreateUsageModel(); var serviceProvider = CreateServiceProvider(); + var vsservices = serviceProvider.GetService(); + vsservices.VSVersion.Returns(model.VSVersion); + var targetAndTick = CreateTargetAndGetTick( serviceProvider, CreateUsageService(model, sameDay: false)); @@ -237,38 +251,102 @@ public async Task MetricserviceSendsAllTheDataCorrectly() await targetAndTick.Item2(); var metricsService = serviceProvider.TryGetService(); + var list = metricsService.ReceivedCalls().Select(x => x.GetMethodInfo().Name); + var result = (UsageModel)metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0]; + + var expected = model.Clone(false, false); + expected.NumberOfStartups++; + + CollectionAssert.AreEquivalent( + ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetFields(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); + } + + [Test] + public async Task MetricserviceSendsWeeklyData() + { + var model = CreateUsageModel(); + var serviceProvider = CreateServiceProvider(); + var vsservices = serviceProvider.GetService(); + vsservices.VSVersion.Returns(model.VSVersion); + + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService(model, sameDay: false, sameWeek: false)); + await targetAndTick.Item2(); + + var metricsService = serviceProvider.TryGetService(); var list = metricsService.ReceivedCalls().Select(x => x.GetMethodInfo().Name); - var result = metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0] as UsageModel; + var result = (UsageModel)metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0]; + + var expected = model.Clone(true, false); + expected.NumberOfStartups++; + expected.NumberOfStartupsWeek++; + + CollectionAssert.AreEquivalent( + ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetFields(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); + } + + [Test] + public async Task MetricserviceSendsMonthlyData() + { + var model = CreateUsageModel(); + var serviceProvider = CreateServiceProvider(); + var vsservices = serviceProvider.GetService(); + vsservices.VSVersion.Returns(model.VSVersion); + + var targetAndTick = CreateTargetAndGetTick( + serviceProvider, + CreateUsageService(model, sameDay: false, sameWeek: false, sameMonth: false)); + + await targetAndTick.Item2(); + + var metricsService = serviceProvider.TryGetService(); + var list = metricsService.ReceivedCalls().Select(x => x.GetMethodInfo().Name); + var result = (UsageModel)metricsService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "PostUsage").GetArguments()[0]; + + var expected = model; + expected.NumberOfStartups++; + expected.NumberOfStartupsWeek++; + expected.NumberOfStartupsMonth++; + CollectionAssert.AreEquivalent( - model.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(model) }), - result.GetType().GetRuntimeProperties().Select(x => new { x.Name, Value = x.GetValue(result) })); + ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetFields(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); } private static UsageModel CreateUsageModel() { var count = 1; - var model = new UsageModel(); - var properties = model.GetType().GetRuntimeProperties(); - foreach (var property in properties) + // UsageModel is a struct so we have to force box it to be able to set values on it + object model = new UsageModel(); + var fields = model.GetType().GetRuntimeFields(); + foreach (var field in fields) { - if (property.PropertyType == typeof(int)) + if (field.FieldType == typeof(int)) { - property.SetValue(model, count++); + field.SetValue(model, count++); } - else if (property.PropertyType == typeof(string)) + else if (field.FieldType == typeof(string)) { - property.SetValue(model, $"string {count++}"); + if (field.Name == "Lang") + field.SetValue(model, CultureInfo.InstalledUICulture.IetfLanguageTag); + else if (field.Name == "AppVersion") + field.SetValue(model, AssemblyVersionInformation.Version); + else + field.SetValue(model, $"string {count++}"); } - else if (property.PropertyType == typeof(bool)) + else if (field.FieldType == typeof(bool)) { - property.SetValue(model, true); + field.SetValue(model, true); } else Assert.Fail("Unknown field type in UsageModel. Fix this test to support it"); } - return model; + return (UsageModel)model; } static Tuple> CreateTargetAndGetTick( @@ -291,12 +369,14 @@ static IGitHubServiceProvider CreateServiceProvider(bool hasMetricsService = tru var connectionManager = Substitute.For(); var metricsService = Substitute.For(); var packageSettings = Substitute.For(); + var vsservices = Substitute.For(); connectionManager.Connections.Returns(new ObservableCollectionEx()); packageSettings.CollectMetrics.Returns(true); result.GetService().Returns(connectionManager); result.GetService().Returns(packageSettings); + result.GetService().Returns(vsservices); result.TryGetService().Returns(hasMetricsService ? metricsService : null); return result; diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index bd5282d9e4..eb05758fe7 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -273,6 +273,7 @@ + @@ -288,7 +289,7 @@ Properties\SolutionInfo.cs - + {B4E665E5-6CAF-4414-A6E2-8DE1C3BCF203} From 72899c8afe0f17ca4a2ad4a953b5a7f69b353c81 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 18:12:45 +0100 Subject: [PATCH 06/15] Same week on different year means different week --- src/GitHub.VisualStudio/Services/UsageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitHub.VisualStudio/Services/UsageService.cs b/src/GitHub.VisualStudio/Services/UsageService.cs index 8deec1f61a..755cee8db6 100644 --- a/src/GitHub.VisualStudio/Services/UsageService.cs +++ b/src/GitHub.VisualStudio/Services/UsageService.cs @@ -32,7 +32,7 @@ public bool IsSameDay(DateTimeOffset lastUpdated) public bool IsSameWeek(DateTimeOffset lastUpdated) { - return GetIso8601WeekOfYear(lastUpdated) == GetIso8601WeekOfYear(DateTimeOffset.Now); + return GetIso8601WeekOfYear(lastUpdated) == GetIso8601WeekOfYear(DateTimeOffset.Now) && lastUpdated.Year != DateTimeOffset.Now.Year; } public bool IsSameMonth(DateTimeOffset lastUpdated) From 2af1abff756fe637ac794baa5dc8ccdc0b29dcb6 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 18:13:51 +0100 Subject: [PATCH 07/15] Missed an added file --- test/UnitTests/Helpers/SimpleJson.cs | 2127 ++++++++++++++++++++++++++ 1 file changed, 2127 insertions(+) create mode 100644 test/UnitTests/Helpers/SimpleJson.cs diff --git a/test/UnitTests/Helpers/SimpleJson.cs b/test/UnitTests/Helpers/SimpleJson.cs new file mode 100644 index 0000000000..9c6f6ed5ab --- /dev/null +++ b/test/UnitTests/Helpers/SimpleJson.cs @@ -0,0 +1,2127 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) 2011, The Outercurve Foundation. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.opensource.org/licenses/mit-license.php +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me) +// https://github.com/facebook-csharp-sdk/simple-json +//----------------------------------------------------------------------- + +// VERSION: 0.38.0 + +// NOTE: uncomment the following line to make SimpleJson class internal. +//#define SIMPLE_JSON_INTERNAL + +// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. +//#define SIMPLE_JSON_OBJARRAYINTERNAL + +// NOTE: uncomment the following line to enable dynamic support. +//#define SIMPLE_JSON_DYNAMIC + +// NOTE: uncomment the following line to enable DataContract support. +//#define SIMPLE_JSON_DATACONTRACT + +// NOTE: uncomment the following line to enable IReadOnlyCollection and IReadOnlyList support. +//#define SIMPLE_JSON_READONLY_COLLECTIONS + +// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). +// define if you are using .net framework <= 3.0 or < WP7.5 +//#define SIMPLE_JSON_NO_LINQ_EXPRESSION + +// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. +// usually already defined in properties +//#define NETFX_CORE; + +// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; + +// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +#if NETFX_CORE +#define SIMPLE_JSON_TYPEINFO +#endif + +using System; +using System.CodeDom.Compiler; +using System.Collections; +using System.Collections.Generic; +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION +using System.Linq.Expressions; +#endif +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +#if SIMPLE_JSON_DYNAMIC +using System.Dynamic; +#endif +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using GitHub.Reflection; + +// ReSharper disable LoopCanBeConvertedToQuery +// ReSharper disable RedundantExplicitArrayCreation +// ReSharper disable SuggestUseVarKeywordEvident +namespace GitHub +{ + /// + /// Represents the json array. + /// + [GeneratedCode("simple-json", "1.0.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if SIMPLE_JSON_OBJARRAYINTERNAL + internal +#else + public +#endif + class JsonArray : List + { + /// + /// Initializes a new instance of the class. + /// + public JsonArray() { } + + /// + /// Initializes a new instance of the class. + /// + /// The capacity of the json array. + public JsonArray(int capacity) : base(capacity) { } + + /// + /// The json representation of the array. + /// + /// The json representation of the array. + public override string ToString() + { + return SimpleJson.SerializeObject(this) ?? string.Empty; + } + } + + /// + /// Represents the json object. + /// + [GeneratedCode("simple-json", "1.0.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if SIMPLE_JSON_OBJARRAYINTERNAL + internal +#else + public +#endif + class JsonObject : +#if SIMPLE_JSON_DYNAMIC + DynamicObject, +#endif + IDictionary + { + /// + /// The internal member dictionary. + /// + private readonly Dictionary _members; + + /// + /// Initializes a new instance of . + /// + public JsonObject() + { + _members = new Dictionary(); + } + + /// + /// Initializes a new instance of . + /// + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + public JsonObject(IEqualityComparer comparer) + { + _members = new Dictionary(comparer); + } + + /// + /// Gets the at the specified index. + /// + /// + public object this[int index] + { + get { return GetAtIndex(_members, index); } + } + + internal static object GetAtIndex(IDictionary obj, int index) + { + if (obj == null) + throw new ArgumentNullException("obj"); + if (index >= obj.Count) + throw new ArgumentOutOfRangeException("index"); + int i = 0; + foreach (KeyValuePair o in obj) + if (i++ == index) return o.Value; + return null; + } + + /// + /// Adds the specified key. + /// + /// The key. + /// The value. + public void Add(string key, object value) + { + _members.Add(key, value); + } + + /// + /// Determines whether the specified key contains key. + /// + /// The key. + /// + /// true if the specified key contains key; otherwise, false. + /// + public bool ContainsKey(string key) + { + return _members.ContainsKey(key); + } + + /// + /// Gets the keys. + /// + /// The keys. + public ICollection Keys + { + get { return _members.Keys; } + } + + /// + /// Removes the specified key. + /// + /// The key. + /// + public bool Remove(string key) + { + return _members.Remove(key); + } + + /// + /// Tries the get value. + /// + /// The key. + /// The value. + /// + public bool TryGetValue(string key, out object value) + { + return _members.TryGetValue(key, out value); + } + + /// + /// Gets the values. + /// + /// The values. + public ICollection Values + { + get { return _members.Values; } + } + + /// + /// Gets or sets the with the specified key. + /// + /// + public object this[string key] + { + get { return _members[key]; } + set { _members[key] = value; } + } + + /// + /// Adds the specified item. + /// + /// The item. + public void Add(KeyValuePair item) + { + _members.Add(item.Key, item.Value); + } + + /// + /// Clears this instance. + /// + public void Clear() + { + _members.Clear(); + } + + /// + /// Determines whether [contains] [the specified item]. + /// + /// The item. + /// + /// true if [contains] [the specified item]; otherwise, false. + /// + public bool Contains(KeyValuePair item) + { + return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; + } + + /// + /// Copies to. + /// + /// The array. + /// Index of the array. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException("array"); + int num = Count; + foreach (KeyValuePair kvp in this) + { + array[arrayIndex++] = kvp; + if (--num <= 0) + return; + } + } + + /// + /// Gets the count. + /// + /// The count. + public int Count + { + get { return _members.Count; } + } + + /// + /// Gets a value indicating whether this instance is read only. + /// + /// + /// true if this instance is read only; otherwise, false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Removes the specified item. + /// + /// The item. + /// + public bool Remove(KeyValuePair item) + { + return _members.Remove(item.Key); + } + + /// + /// Gets the enumerator. + /// + /// + public IEnumerator> GetEnumerator() + { + return _members.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _members.GetEnumerator(); + } + + /// + /// Returns a json that represents the current . + /// + /// + /// A json that represents the current . + /// + public override string ToString() + { + return SimpleJson.SerializeObject(this); + } + +#if SIMPLE_JSON_DYNAMIC + /// + /// Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. + /// + /// Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. + /// The result of the type conversion operation. + /// + /// Alwasy returns true. + /// + public override bool TryConvert(ConvertBinder binder, out object result) + { + // + if (binder == null) + throw new ArgumentNullException("binder"); + // + Type targetType = binder.Type; + + if ((targetType == typeof(IEnumerable)) || + (targetType == typeof(IEnumerable>)) || + (targetType == typeof(IDictionary)) || + (targetType == typeof(IDictionary))) + { + result = this; + return true; + } + + return base.TryConvert(binder, out result); + } + + /// + /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. + /// + /// Provides information about the deletion. + /// + /// Alwasy returns true. + /// + public override bool TryDeleteMember(DeleteMemberBinder binder) + { + // + if (binder == null) + throw new ArgumentNullException("binder"); + // + return _members.Remove(binder.Name); + } + + /// + /// Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. + /// + /// Provides information about the operation. + /// The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. + /// The result of the index operation. + /// + /// Alwasy returns true. + /// + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + if (indexes == null) throw new ArgumentNullException("indexes"); + if (indexes.Length == 1) + { + result = ((IDictionary)this)[(string)indexes[0]]; + return true; + } + result = null; + return true; + } + + /// + /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. + /// + /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . + /// + /// Alwasy returns true. + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + object value; + if (_members.TryGetValue(binder.Name, out value)) + { + result = value; + return true; + } + result = null; + return true; + } + + /// + /// Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. + /// + /// Provides information about the operation. + /// The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. + /// The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. + /// + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + { + if (indexes == null) throw new ArgumentNullException("indexes"); + if (indexes.Length == 1) + { + ((IDictionary)this)[(string)indexes[0]] = value; + return true; + } + return base.TrySetIndex(binder, indexes, value); + } + + /// + /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. + /// + /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + // + if (binder == null) + throw new ArgumentNullException("binder"); + // + _members[binder.Name] = value; + return true; + } + + /// + /// Returns the enumeration of all dynamic member names. + /// + /// + /// A sequence that contains dynamic member names. + /// + public override IEnumerable GetDynamicMemberNames() + { + foreach (var key in Keys) + yield return key; + } +#endif + } +} + +namespace GitHub +{ + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). + /// All numbers are parsed to doubles. + /// + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + static class SimpleJson + { + private const int TOKEN_NONE = 0; + private const int TOKEN_CURLY_OPEN = 1; + private const int TOKEN_CURLY_CLOSE = 2; + private const int TOKEN_SQUARED_OPEN = 3; + private const int TOKEN_SQUARED_CLOSE = 4; + private const int TOKEN_COLON = 5; + private const int TOKEN_COMMA = 6; + private const int TOKEN_STRING = 7; + private const int TOKEN_NUMBER = 8; + private const int TOKEN_TRUE = 9; + private const int TOKEN_FALSE = 10; + private const int TOKEN_NULL = 11; + private const int BUILDER_CAPACITY = 2000; + + private static readonly char[] EscapeTable; + private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; + private static readonly string EscapeCharactersString = new string(EscapeCharacters); + + static SimpleJson() + { + EscapeTable = new char[93]; + EscapeTable['"'] = '"'; + EscapeTable['\\'] = '\\'; + EscapeTable['\b'] = 'b'; + EscapeTable['\f'] = 'f'; + EscapeTable['\n'] = 'n'; + EscapeTable['\r'] = 'r'; + EscapeTable['\t'] = 't'; + } + + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false + public static object DeserializeObject(string json) + { + object obj; + if (TryDeserializeObject(json, out obj)) + return obj; + throw new SerializationException("Invalid JSON string"); + } + + /// + /// Try parsing the json string into a value. + /// + /// + /// A JSON string. + /// + /// + /// The object. + /// + /// + /// Returns true if successfull otherwise false. + /// + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + public static bool TryDeserializeObject(string json, out object obj) + { + bool success = true; + if (json != null) + { + char[] charArray = json.ToCharArray(); + int index = 0; + obj = ParseValue(charArray, ref index, ref success); + } + else + obj = null; + + return success; + } + + public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) + { + object jsonObject = DeserializeObject(json); + return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) + ? jsonObject + : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); + } + + public static object DeserializeObject(string json, Type type) + { + return DeserializeObject(json, type, null); + } + + public static T DeserializeObject(string json, IJsonSerializerStrategy jsonSerializerStrategy) + { + return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); + } + + public static T DeserializeObject(string json) + { + return (T)DeserializeObject(json, typeof(T), null); + } + + /// + /// Converts a IDictionary<string,object> / IList<object> object into a JSON string + /// + /// A IDictionary<string,object> / IList<object> + /// Serializer strategy to use + /// A JSON encoded string, or null if object 'json' is not serializable + public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) + { + StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); + bool success = SerializeValue(jsonSerializerStrategy, json, builder); + return (success ? builder.ToString() : null); + } + + public static string SerializeObject(object json) + { + return SerializeObject(json, CurrentJsonSerializerStrategy); + } + + public static string EscapeToJavascriptString(string jsonString) + { + if (string.IsNullOrEmpty(jsonString)) + return jsonString; + + StringBuilder sb = new StringBuilder(); + char c; + + for (int i = 0; i < jsonString.Length; ) + { + c = jsonString[i++]; + + if (c == '\\') + { + int remainingLength = jsonString.Length - i; + if (remainingLength >= 2) + { + char lookahead = jsonString[i]; + if (lookahead == '\\') + { + sb.Append('\\'); + ++i; + } + else if (lookahead == '"') + { + sb.Append("\""); + ++i; + } + else if (lookahead == 't') + { + sb.Append('\t'); + ++i; + } + else if (lookahead == 'b') + { + sb.Append('\b'); + ++i; + } + else if (lookahead == 'n') + { + sb.Append('\n'); + ++i; + } + else if (lookahead == 'r') + { + sb.Append('\r'); + ++i; + } + } + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + + static IDictionary ParseObject(char[] json, ref int index, ref bool success) + { + IDictionary table = new JsonObject(); + int token; + + // { + NextToken(json, ref index); + + bool done = false; + while (!done) + { + token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_CURLY_CLOSE) + { + NextToken(json, ref index); + return table; + } + else + { + // name + string name = ParseString(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + // : + token = NextToken(json, ref index); + if (token != TOKEN_COLON) + { + success = false; + return null; + } + // value + object value = ParseValue(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + table[name] = value; + } + } + return table; + } + + static JsonArray ParseArray(char[] json, ref int index, ref bool success) + { + JsonArray array = new JsonArray(); + + // [ + NextToken(json, ref index); + + bool done = false; + while (!done) + { + int token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_SQUARED_CLOSE) + { + NextToken(json, ref index); + break; + } + else + { + object value = ParseValue(json, ref index, ref success); + if (!success) + return null; + array.Add(value); + } + } + return array; + } + + static object ParseValue(char[] json, ref int index, ref bool success) + { + switch (LookAhead(json, index)) + { + case TOKEN_STRING: + return ParseString(json, ref index, ref success); + case TOKEN_NUMBER: + return ParseNumber(json, ref index, ref success); + case TOKEN_CURLY_OPEN: + return ParseObject(json, ref index, ref success); + case TOKEN_SQUARED_OPEN: + return ParseArray(json, ref index, ref success); + case TOKEN_TRUE: + NextToken(json, ref index); + return true; + case TOKEN_FALSE: + NextToken(json, ref index); + return false; + case TOKEN_NULL: + NextToken(json, ref index); + return null; + case TOKEN_NONE: + break; + } + success = false; + return null; + } + + static string ParseString(char[] json, ref int index, ref bool success) + { + StringBuilder s = new StringBuilder(BUILDER_CAPACITY); + char c; + + EatWhitespace(json, ref index); + + // " + c = json[index++]; + bool complete = false; + while (!complete) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + c = json[index++]; + if (c == '"') + s.Append('"'); + else if (c == '\\') + s.Append('\\'); + else if (c == '/') + s.Append('/'); + else if (c == 'b') + s.Append('\b'); + else if (c == 'f') + s.Append('\f'); + else if (c == 'n') + s.Append('\n'); + else if (c == 'r') + s.Append('\r'); + else if (c == 't') + s.Append('\t'); + else if (c == 'u') + { + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + // parse the 32 bit hex into an integer codepoint + uint codePoint; + if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) + return ""; + + // convert the integer codepoint to a unicode char and add to string + if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate + { + index += 4; // skip 4 chars + remainingLength = json.Length - index; + if (remainingLength >= 6) + { + uint lowCodePoint; + if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) + { + if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate + { + s.Append((char)codePoint); + s.Append((char)lowCodePoint); + index += 6; // skip 6 chars + continue; + } + } + } + success = false; // invalid surrogate pair + return ""; + } + s.Append(ConvertFromUtf32((int)codePoint)); + // skip 4 chars + index += 4; + } + else + break; + } + } + else + s.Append(c); + } + if (!complete) + { + success = false; + return null; + } + return s.ToString(); + } + + private static string ConvertFromUtf32(int utf32) + { + // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm + if (utf32 < 0 || utf32 > 0x10FFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); + if (0xD800 <= utf32 && utf32 <= 0xDFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); + if (utf32 < 0x10000) + return new string((char)utf32, 1); + utf32 -= 0x10000; + return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); + } + + static object ParseNumber(char[] json, ref int index, ref bool success) + { + EatWhitespace(json, ref index); + int lastIndex = GetLastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + object returnNumber; + string str = new string(json, index, charLength); + if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) + { + double number; + success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); + returnNumber = number; + } + else + { + long number; + success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); + returnNumber = number; + } + index = lastIndex + 1; + return returnNumber; + } + + static int GetLastIndexOfNumber(char[] json, int index) + { + int lastIndex; + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; + return lastIndex - 1; + } + + static void EatWhitespace(char[] json, ref int index) + { + for (; index < json.Length; index++) + if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; + } + + static int LookAhead(char[] json, int index) + { + int saveIndex = index; + return NextToken(json, ref saveIndex); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + static int NextToken(char[] json, ref int index) + { + EatWhitespace(json, ref index); + if (index == json.Length) + return TOKEN_NONE; + char c = json[index]; + index++; + switch (c) + { + case '{': + return TOKEN_CURLY_OPEN; + case '}': + return TOKEN_CURLY_CLOSE; + case '[': + return TOKEN_SQUARED_OPEN; + case ']': + return TOKEN_SQUARED_CLOSE; + case ',': + return TOKEN_COMMA; + case '"': + return TOKEN_STRING; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN_NUMBER; + case ':': + return TOKEN_COLON; + } + index--; + int remainingLength = json.Length - index; + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') + { + index += 5; + return TOKEN_FALSE; + } + } + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') + { + index += 4; + return TOKEN_TRUE; + } + } + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') + { + index += 4; + return TOKEN_NULL; + } + } + return TOKEN_NONE; + } + + static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) + { + bool success = true; + string stringValue = value as string; + if (stringValue != null) + success = SerializeString(stringValue, builder); + else + { + IDictionary dict = value as IDictionary; + if (dict != null) + { + success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); + } + else + { + IDictionary stringDictionary = value as IDictionary; + if (stringDictionary != null) + { + success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); + } + else + { + IEnumerable enumerableValue = value as IEnumerable; + if (enumerableValue != null) + success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); + else if (IsNumeric(value)) + success = SerializeNumber(value, builder); + else if (value is bool) + builder.Append((bool)value ? "true" : "false"); + else if (value == null) + builder.Append("null"); + else + { + object serializedObject; + success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); + if (success) + SerializeValue(jsonSerializerStrategy, serializedObject, builder); + } + } + } + } + return success; + } + + static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) + { + builder.Append("{"); + IEnumerator ke = keys.GetEnumerator(); + IEnumerator ve = values.GetEnumerator(); + bool first = true; + while (ke.MoveNext() && ve.MoveNext()) + { + object key = ke.Current; + object value = ve.Current; + if (!first) + builder.Append(","); + string stringKey = key as string; + if (stringKey != null) + SerializeString(stringKey, builder); + else + if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; + builder.Append(":"); + if (!SerializeValue(jsonSerializerStrategy, value, builder)) + return false; + first = false; + } + builder.Append("}"); + return true; + } + + static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) + { + builder.Append("["); + bool first = true; + foreach (object value in anArray) + { + if (!first) + builder.Append(","); + if (!SerializeValue(jsonSerializerStrategy, value, builder)) + return false; + first = false; + } + builder.Append("]"); + return true; + } + + static bool SerializeString(string aString, StringBuilder builder) + { + // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) + if (aString.IndexOfAny(EscapeCharacters) == -1) + { + builder.Append('"'); + builder.Append(aString); + builder.Append('"'); + + return true; + } + + builder.Append('"'); + int safeCharacterCount = 0; + char[] charArray = aString.ToCharArray(); + + for (int i = 0; i < charArray.Length; i++) + { + char c = charArray[i]; + + // Non ascii characters are fine, buffer them up and send them to the builder + // in larger chunks if possible. The escape table is a 1:1 translation table + // with \0 [default(char)] denoting a safe character. + if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) + { + safeCharacterCount++; + } + else + { + if (safeCharacterCount > 0) + { + builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); + safeCharacterCount = 0; + } + + builder.Append('\\'); + builder.Append(EscapeTable[c]); + } + } + + if (safeCharacterCount > 0) + { + builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); + } + + builder.Append('"'); + return true; + } + + static bool SerializeNumber(object number, StringBuilder builder) + { + if (number is long) + builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); + else if (number is ulong) + builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); + else if (number is int) + builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); + else if (number is uint) + builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); + else if (number is decimal) + builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); + else if (number is float) + builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); + else + builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); + return true; + } + + /// + /// Determines if a given object is numeric in any way + /// (can be integer, double, null, etc). + /// + static bool IsNumeric(object value) + { + if (value is sbyte) return true; + if (value is byte) return true; + if (value is short) return true; + if (value is ushort) return true; + if (value is int) return true; + if (value is uint) return true; + if (value is long) return true; + if (value is ulong) return true; + if (value is float) return true; + if (value is double) return true; + if (value is decimal) return true; + return false; + } + + private static IJsonSerializerStrategy _currentJsonSerializerStrategy; + public static IJsonSerializerStrategy CurrentJsonSerializerStrategy + { + get + { + return _currentJsonSerializerStrategy ?? + (_currentJsonSerializerStrategy = +#if SIMPLE_JSON_DATACONTRACT + DataContractJsonSerializerStrategy +#else + PocoJsonSerializerStrategy +#endif +); + } + set + { + _currentJsonSerializerStrategy = value; + } + } + + private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy + { + get + { + return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); + } + } + +#if SIMPLE_JSON_DATACONTRACT + + private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; + [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] + public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy + { + get + { + return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); + } + } + +#endif + } + + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + interface IJsonSerializerStrategy + { + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + bool TrySerializeNonPrimitiveObject(object input, out object output); + object DeserializeObject(object value, Type type); + } + + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + class PocoJsonSerializerStrategy : IJsonSerializerStrategy + { + internal IDictionary ConstructorCache; + internal IDictionary> GetCache; + internal IDictionary>> SetCache; + + internal static readonly Type[] EmptyTypes = new Type[0]; + internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; + + private static readonly string[] Iso8601Format = new string[] + { + @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", + @"yyyy-MM-dd\THH:mm:ss\Z", + @"yyyy-MM-dd\THH:mm:ssK" + }; + + public PocoJsonSerializerStrategy() + { + ConstructorCache = new ReflectionUtils.ThreadSafeDictionary(ContructorDelegateFactory); + GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); + SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); + } + + protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) + { + return clrPropertyName; + } + + internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key) + { + return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes); + } + + internal virtual IDictionary GetterValueFactory(Type type) + { + IDictionary result = new Dictionary(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanRead) + { + MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (getMethod.IsStatic || !getMethod.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); + } + return result; + } + + internal virtual IDictionary> SetterValueFactory(Type type) + { + IDictionary> result = new Dictionary>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanWrite) + { + MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); + if (setMethod.IsStatic || !setMethod.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); + } + return result; + } + + public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) + { + return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public virtual object DeserializeObject(object value, Type type) + { + if (type == null) throw new ArgumentNullException("type"); + string str = value as string; + + if (type == typeof (Guid) && string.IsNullOrEmpty(str)) + return default(Guid); + + if (value == null) + return null; + + object obj = null; + + if (str != null) + { + if (str.Length != 0) // We know it can't be null now. + { + if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) + return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) + return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) + return new Guid(str); + if (type == typeof(Uri)) + { + bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); + + Uri result; + if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) + return result; + + return null; + } + + if (type == typeof(string)) + return str; + + return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); + } + else + { + if (type == typeof(Guid)) + obj = default(Guid); + else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) + obj = null; + else + obj = str; + } + // Empty string case + if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) + return str; + } + else if (value is bool) + return value; + + bool valueIsLong = value is long; + bool valueIsDouble = value is double; + if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) + return value; + if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) + { + obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) + ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) + : value; + } + else + { + IDictionary objects = value as IDictionary; + if (objects != null) + { + IDictionary jsonObject = objects; + + if (ReflectionUtils.IsTypeDictionary(type)) + { + // if dictionary then + Type[] types = ReflectionUtils.GetGenericTypeArguments(type); + Type keyType = types[0]; + Type valueType = types[1]; + + Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + + IDictionary dict = (IDictionary)ConstructorCache[genericType](); + + foreach (KeyValuePair kvp in jsonObject) + dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); + + obj = dict; + } + else + { + if (type == typeof(object)) + obj = value; + else + { + obj = ConstructorCache[type](); + foreach (KeyValuePair> setter in SetCache[type]) + { + object jsonValue; + if (jsonObject.TryGetValue(setter.Key, out jsonValue)) + { + jsonValue = DeserializeObject(jsonValue, setter.Value.Key); + setter.Value.Value(obj, jsonValue); + } + } + } + } + } + else + { + IList valueAsList = value as IList; + if (valueAsList != null) + { + IList jsonObject = valueAsList; + IList list = null; + + if (type.IsArray) + { + list = (IList)ConstructorCache[type](jsonObject.Count); + int i = 0; + foreach (object o in jsonObject) + list[i++] = DeserializeObject(o, type.GetElementType()); + } + else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) + { + Type innerType = ReflectionUtils.GetGenericListElementType(type); + list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); + foreach (object o in jsonObject) + list.Add(DeserializeObject(o, innerType)); + } + obj = list; + } + } + return obj; + } + if (ReflectionUtils.IsNullableType(type)) + return ReflectionUtils.ToNullableType(obj, type); + return obj; + } + + protected virtual object SerializeEnum(Enum p) + { + return Convert.ToDouble(p, CultureInfo.InvariantCulture); + } + + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + protected virtual bool TrySerializeKnownTypes(object input, out object output) + { + bool returnValue = true; + if (input is DateTime) + output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); + else if (input is DateTimeOffset) + output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); + else if (input is Guid) + output = ((Guid)input).ToString("D"); + else if (input is Uri) + output = input.ToString(); + else + { + Enum inputEnum = input as Enum; + if (inputEnum != null) + output = SerializeEnum(inputEnum); + else + { + returnValue = false; + output = null; + } + } + return returnValue; + } + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + protected virtual bool TrySerializeUnknownTypes(object input, out object output) + { + if (input == null) throw new ArgumentNullException("input"); + output = null; + Type type = input.GetType(); + if (type.FullName == null) + return false; + IDictionary obj = new JsonObject(); + IDictionary getters = GetCache[type]; + foreach (KeyValuePair getter in getters) + { + if (getter.Value != null) + obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); + } + output = obj; + return true; + } + } + +#if SIMPLE_JSON_DATACONTRACT + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy + { + public DataContractJsonSerializerStrategy() + { + GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); + SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); + } + + internal override IDictionary GetterValueFactory(Type type) + { + bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; + if (!hasDataContract) + return base.GetterValueFactory(type); + string jsonKey; + IDictionary result = new Dictionary(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanRead) + { + MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) + result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) + result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); + } + return result; + } + + internal override IDictionary> SetterValueFactory(Type type) + { + bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; + if (!hasDataContract) + return base.SetterValueFactory(type); + string jsonKey; + IDictionary> result = new Dictionary>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanWrite) + { + MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); + if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) + result[jsonKey] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) + result[jsonKey] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); + } + // todo implement sorting for DATACONTRACT. + return result; + } + + private static bool CanAdd(MemberInfo info, out string jsonKey) + { + jsonKey = null; + if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) + return false; + DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); + if (dataMemberAttribute == null) + return false; + jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; + return true; + } + } + +#endif + + namespace Reflection + { + // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules + // that might be in place in the target project. + [GeneratedCode("reflection-utils", "1.0.0")] +#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC + public +#else + internal +#endif + class ReflectionUtils + { + private static readonly object[] EmptyObjects = new object[] { }; + + public delegate object GetDelegate(object source); + public delegate void SetDelegate(object source, object value); + public delegate object ConstructorDelegate(params object[] args); + + public delegate TValue ThreadSafeDictionaryValueFactory(TKey key); + +#if SIMPLE_JSON_TYPEINFO + public static TypeInfo GetTypeInfo(Type type) + { + return type.GetTypeInfo(); + } +#else + public static Type GetTypeInfo(Type type) + { + return type; + } +#endif + + public static Attribute GetAttribute(MemberInfo info, Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (info == null || type == null || !info.IsDefined(type)) + return null; + return info.GetCustomAttribute(type); +#else + if (info == null || type == null || !Attribute.IsDefined(info, type)) + return null; + return Attribute.GetCustomAttribute(info, type); +#endif + } + + public static Type GetGenericListElementType(Type type) + { + IEnumerable interfaces; +#if SIMPLE_JSON_TYPEINFO + interfaces = type.GetTypeInfo().ImplementedInterfaces; +#else + interfaces = type.GetInterfaces(); +#endif + foreach (Type implementedInterface in interfaces) + { + if (IsTypeGeneric(implementedInterface) && + implementedInterface.GetGenericTypeDefinition() == typeof (IList<>)) + { + return GetGenericTypeArguments(implementedInterface)[0]; + } + } + return GetGenericTypeArguments(type)[0]; + } + + public static Attribute GetAttribute(Type objectType, Type attributeType) + { + +#if SIMPLE_JSON_TYPEINFO + if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) + return null; + return objectType.GetTypeInfo().GetCustomAttribute(attributeType); +#else + if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) + return null; + return Attribute.GetCustomAttribute(objectType, attributeType); +#endif + } + + public static Type[] GetGenericTypeArguments(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().GenericTypeArguments; +#else + return type.GetGenericArguments(); +#endif + } + + public static bool IsTypeGeneric(Type type) + { + return GetTypeInfo(type).IsGenericType; + } + + public static bool IsTypeGenericeCollectionInterface(Type type) + { + if (!IsTypeGeneric(type)) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(IList<>) + || genericDefinition == typeof(ICollection<>) + || genericDefinition == typeof(IEnumerable<>) +#if SIMPLE_JSON_READONLY_COLLECTIONS + || genericDefinition == typeof(IReadOnlyCollection<>) + || genericDefinition == typeof(IReadOnlyList<>) +#endif + ); + } + + public static bool IsAssignableFrom(Type type1, Type type2) + { + return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); + } + + public static bool IsTypeDictionary(Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + return true; +#else + if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) + return true; +#endif + if (!GetTypeInfo(type).IsGenericType) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + return genericDefinition == typeof(IDictionary<,>); + } + + public static bool IsNullableType(Type type) + { + return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + public static object ToNullableType(object obj, Type nullableType) + { + return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); + } + + public static bool IsValueType(Type type) + { + return GetTypeInfo(type).IsValueType; + } + + public static IEnumerable GetConstructors(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().DeclaredConstructors; +#else + return type.GetConstructors(); +#endif + } + + public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) + { + IEnumerable constructorInfos = GetConstructors(type); + int i; + bool matches; + foreach (ConstructorInfo constructorInfo in constructorInfos) + { + ParameterInfo[] parameters = constructorInfo.GetParameters(); + if (argsType.Length != parameters.Length) + continue; + + i = 0; + matches = true; + foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) + { + if (parameterInfo.ParameterType != argsType[i]) + { + matches = false; + break; + } + } + + if (matches) + return constructorInfo; + } + + return null; + } + + public static IEnumerable GetProperties(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeProperties(); +#else + return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static IEnumerable GetFields(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeFields(); +#else + return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.GetMethod; +#else + return propertyInfo.GetGetMethod(true); +#endif + } + + public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.SetMethod; +#else + return propertyInfo.GetSetMethod(true); +#endif + } + + public static ConstructorDelegate GetContructor(ConstructorInfo constructorInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(constructorInfo); +#else + return GetConstructorByExpression(constructorInfo); +#endif + } + + public static ConstructorDelegate GetContructor(Type type, params Type[] argsType) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(type, argsType); +#else + return GetConstructorByExpression(type, argsType); +#endif + } + + public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) + { + return delegate(object[] args) { return constructorInfo.Invoke(args); }; + } + + public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) + { + ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); + ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); + Expression[] argsExp = new Expression[paramsInfo.Length]; + for (int i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + Expression paramAccessorExp = Expression.ArrayIndex(param, index); + Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); + argsExp[i] = paramCastExp; + } + NewExpression newExp = Expression.New(constructorInfo, argsExp); + Expression> lambda = Expression.Lambda>(newExp, param); + Func compiledLambda = lambda.Compile(); + return delegate(object[] args) { return compiledLambda(args); }; + } + + public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); + } + +#endif + + public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(propertyInfo); +#else + return GetGetMethodByExpression(propertyInfo); +#endif + } + + public static GetDelegate GetGetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(fieldInfo); +#else + return GetGetMethodByExpression(fieldInfo); +#endif + } + + public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); + return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; + } + + public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source) { return fieldInfo.GetValue(source); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + Func compiled = Expression.Lambda>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + + public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); + GetDelegate compiled = Expression.Lambda(Expression.Convert(member, typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + +#endif + + public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(propertyInfo); +#else + return GetSetMethodByExpression(propertyInfo); +#endif + } + + public static SetDelegate GetSetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(fieldInfo); +#else + return GetSetMethodByExpression(fieldInfo); +#endif + } + + public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); + return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; + } + + public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); + Action compiled = Expression.Lambda>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + Action compiled = Expression.Lambda>( + Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static BinaryExpression Assign(Expression left, Expression right) + { +#if SIMPLE_JSON_TYPEINFO + return Expression.Assign(left, right); +#else + MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); + BinaryExpression assignExpr = Expression.Add(left, right, assign); + return assignExpr; +#endif + } + + private static class Assigner + { + public static T Assign(ref T left, T right) + { + return (left = right); + } + } + +#endif + + public sealed class ThreadSafeDictionary : IDictionary + { + private readonly object _lock = new object(); + private readonly ThreadSafeDictionaryValueFactory _valueFactory; + private Dictionary _dictionary; + + public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory valueFactory) + { + _valueFactory = valueFactory; + } + + private TValue Get(TKey key) + { + if (_dictionary == null) + return AddValue(key); + TValue value; + if (!_dictionary.TryGetValue(key, out value)) + return AddValue(key); + return value; + } + + private TValue AddValue(TKey key) + { + TValue value = _valueFactory(key); + lock (_lock) + { + if (_dictionary == null) + { + _dictionary = new Dictionary(); + _dictionary[key] = value; + } + else + { + TValue val; + if (_dictionary.TryGetValue(key, out val)) + return val; + Dictionary dict = new Dictionary(_dictionary); + dict[key] = value; + _dictionary = dict; + } + } + return value; + } + + public void Add(TKey key, TValue value) + { + throw new NotImplementedException(); + } + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public ICollection Keys + { + get { return _dictionary.Keys; } + } + + public bool Remove(TKey key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { + value = this[key]; + return true; + } + + public ICollection Values + { + get { return _dictionary.Values; } + } + + public TValue this[TKey key] + { + get { return Get(key); } + set { throw new NotImplementedException(); } + } + + public void Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public int Count + { + get { return _dictionary.Count; } + } + + public bool IsReadOnly + { + get { throw new NotImplementedException(); } + } + + public bool Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public IEnumerator> GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + } + + } + } +} +// ReSharper restore LoopCanBeConvertedToQuery +// ReSharper restore RedundantExplicitArrayCreation +// ReSharper restore SuggestUseVarKeywordEvident From a64321ff8947500457228b8dfa3b1dc2047b772a Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 18:39:47 +0100 Subject: [PATCH 08/15] Suppress CA warning, we don't care --- src/GitHub.Exports/Models/UsageModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs index 11c70ea7d8..cd0e5fce30 100644 --- a/src/GitHub.Exports/Models/UsageModel.cs +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -3,6 +3,7 @@ namespace GitHub.Models { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "It'll use reflection by default and we're fine with that")] public struct UsageModel { public bool IsGitHubUser; From 2380135901e7b3b3a6aa45bb41a67e94cad16bd4 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 19:57:08 +0100 Subject: [PATCH 09/15] Go back to using autoprops. Remove unused code in SimpleJson. --- src/GitHub.Exports/Models/UsageModel.cs | 75 +- .../Services/UsageTracker.cs | 8 +- .../Services/UsageTrackerTests.cs | 43 +- test/UnitTests/Helpers/SimpleJson.cs | 1520 ----------------- 4 files changed, 62 insertions(+), 1584 deletions(-) diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs index cd0e5fce30..3219ee7c8e 100644 --- a/src/GitHub.Exports/Models/UsageModel.cs +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -1,46 +1,43 @@ -using System; -using System.Reflection; - -namespace GitHub.Models +namespace GitHub.Models { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "It'll use reflection by default and we're fine with that")] public struct UsageModel { - public bool IsGitHubUser; - public bool IsEnterpriseUser; - public string AppVersion; - public string VSVersion; - public string Lang; - public int NumberOfStartups; - public int NumberOfStartupsWeek; - public int NumberOfStartupsMonth; - public int NumberOfUpstreamPullRequests; - public int NumberOfClones; - public int NumberOfReposCreated; - public int NumberOfReposPublished; - public int NumberOfGists; - public int NumberOfOpenInGitHub; - public int NumberOfLinkToGitHub; - public int NumberOfLogins; - public int NumberOfOAuthLogins; - public int NumberOfTokenLogins; - public int NumberOfPullRequestsOpened; - public int NumberOfLocalPullRequestsCheckedOut; - public int NumberOfLocalPullRequestPulls; - public int NumberOfLocalPullRequestPushes; - public int NumberOfForkPullRequestsCheckedOut; - public int NumberOfForkPullRequestPulls; - public int NumberOfForkPullRequestPushes; - public int NumberOfWelcomeDocsClicks; - public int NumberOfWelcomeTrainingClicks; - public int NumberOfGitHubPaneHelpClicks; - public int NumberOfPRDetailsViewChanges; - public int NumberOfPRDetailsViewFile; - public int NumberOfPRDetailsCompareWithSolution; - public int NumberOfPRDetailsOpenFileInSolution; - public int NumberOfPRDetailsNavigateToEditor; - public int NumberOfPRReviewDiffViewInlineCommentOpen; - public int NumberOfPRReviewDiffViewInlineCommentPost; + public bool IsGitHubUser { get; set; } + public bool IsEnterpriseUser { get; set; } + public string AppVersion { get; set; } + public string VSVersion { get; set; } + public string Lang { get; set; } + public int NumberOfStartups { get; set; } + public int NumberOfStartupsWeek { get; set; } + public int NumberOfStartupsMonth { get; set; } + public int NumberOfUpstreamPullRequests { get; set; } + public int NumberOfClones { get; set; } + public int NumberOfReposCreated { get; set; } + public int NumberOfReposPublished { get; set; } + public int NumberOfGists { get; set; } + public int NumberOfOpenInGitHub { get; set; } + public int NumberOfLinkToGitHub { get; set; } + public int NumberOfLogins { get; set; } + public int NumberOfOAuthLogins { get; set; } + public int NumberOfTokenLogins { get; set; } + public int NumberOfPullRequestsOpened { get; set; } + public int NumberOfLocalPullRequestsCheckedOut { get; set; } + public int NumberOfLocalPullRequestPulls { get; set; } + public int NumberOfLocalPullRequestPushes { get; set; } + public int NumberOfForkPullRequestsCheckedOut { get; set; } + public int NumberOfForkPullRequestPulls { get; set; } + public int NumberOfForkPullRequestPushes { get; set; } + public int NumberOfWelcomeDocsClicks { get; set; } + public int NumberOfWelcomeTrainingClicks { get; set; } + public int NumberOfGitHubPaneHelpClicks { get; set; } + public int NumberOfPRDetailsViewChanges { get; set; } + public int NumberOfPRDetailsViewFile { get; set; } + public int NumberOfPRDetailsCompareWithSolution { get; set; } + public int NumberOfPRDetailsOpenFileInSolution { get; set; } + public int NumberOfPRDetailsNavigateToEditor { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentOpen { get; set; } + public int NumberOfPRReviewDiffViewInlineCommentPost { get; set; } public UsageModel Clone(bool includeWeekly, bool includeMonthly) { diff --git a/src/GitHub.VisualStudio/Services/UsageTracker.cs b/src/GitHub.VisualStudio/Services/UsageTracker.cs index bd4220afe0..5e489ae816 100644 --- a/src/GitHub.VisualStudio/Services/UsageTracker.cs +++ b/src/GitHub.VisualStudio/Services/UsageTracker.cs @@ -46,10 +46,10 @@ public async Task IncrementCounter(Expression> counter) var usage = await LoadUsage(); // because Model is a struct, it needs to be boxed in order for reflection to work object model = usage.Model; - var field = (MemberExpression)counter.Body; - var fieldInfo = (FieldInfo)field.Member; - var value = (int)fieldInfo.GetValue(model); - fieldInfo.SetValue(model, value + 1); + var property = (MemberExpression)counter.Body; + var propertyInfo = (PropertyInfo)property.Member; + var value = (int)propertyInfo.GetValue(model); + propertyInfo.SetValue(model, value + 1); usage.Model = (UsageModel)model; await service.WriteLocalData(usage); } diff --git a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs index fc9f5d02e2..582043ee1f 100644 --- a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs +++ b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Globalization; using GitHub.Reflection; +using GitHub; namespace MetricsTests { @@ -230,10 +231,10 @@ public async Task UsageServiceWritesAllTheDataCorrectly() expected.NumberOfStartupsWeek++; expected.NumberOfStartupsMonth++; - UsageData result = usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData; + var result = (usageService.ReceivedCalls().First(x => x.GetMethodInfo().Name == "WriteLocalData").GetArguments()[0] as UsageData).Model; CollectionAssert.AreEquivalent( - ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), - ReflectionUtils.GetFields(result.Model.GetType()).Select(x => new { x.Name, Value = x.GetValue(result.Model) })); + ReflectionUtils.GetProperties(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetProperties(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); } [Test] @@ -258,8 +259,8 @@ public async Task MetricserviceSendsDailyData() expected.NumberOfStartups++; CollectionAssert.AreEquivalent( - ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), - ReflectionUtils.GetFields(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); + ReflectionUtils.GetProperties(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetProperties(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); } [Test] @@ -285,8 +286,8 @@ public async Task MetricserviceSendsWeeklyData() expected.NumberOfStartupsWeek++; CollectionAssert.AreEquivalent( - ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), - ReflectionUtils.GetFields(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); + ReflectionUtils.GetProperties(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetProperties(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); } [Test] @@ -313,8 +314,8 @@ public async Task MetricserviceSendsMonthlyData() expected.NumberOfStartupsMonth++; CollectionAssert.AreEquivalent( - ReflectionUtils.GetFields(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), - ReflectionUtils.GetFields(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); + ReflectionUtils.GetProperties(expected.GetType()).Select(x => new { x.Name, Value = x.GetValue(expected) }), + ReflectionUtils.GetProperties(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); } private static UsageModel CreateUsageModel() @@ -322,25 +323,25 @@ private static UsageModel CreateUsageModel() var count = 1; // UsageModel is a struct so we have to force box it to be able to set values on it object model = new UsageModel(); - var fields = model.GetType().GetRuntimeFields(); - foreach (var field in fields) + var props = ReflectionUtils.GetProperties(model.GetType()); + foreach (var prop in props) { - if (field.FieldType == typeof(int)) + if (prop.PropertyType == typeof(int)) { - field.SetValue(model, count++); + prop.SetValue(model, count++); } - else if (field.FieldType == typeof(string)) + else if (prop.PropertyType == typeof(string)) { - if (field.Name == "Lang") - field.SetValue(model, CultureInfo.InstalledUICulture.IetfLanguageTag); - else if (field.Name == "AppVersion") - field.SetValue(model, AssemblyVersionInformation.Version); + if (prop.Name == "Lang") + prop.SetValue(model, CultureInfo.InstalledUICulture.IetfLanguageTag); + else if (prop.Name == "AppVersion") + prop.SetValue(model, AssemblyVersionInformation.Version); else - field.SetValue(model, $"string {count++}"); + prop.SetValue(model, $"string {count++}"); } - else if (field.FieldType == typeof(bool)) + else if (prop.PropertyType == typeof(bool)) { - field.SetValue(model, true); + prop.SetValue(model, true); } else Assert.Fail("Unknown field type in UsageModel. Fix this test to support it"); diff --git a/test/UnitTests/Helpers/SimpleJson.cs b/test/UnitTests/Helpers/SimpleJson.cs index 9c6f6ed5ab..d2fe9ca4f5 100644 --- a/test/UnitTests/Helpers/SimpleJson.cs +++ b/test/UnitTests/Helpers/SimpleJson.cs @@ -73,1526 +73,6 @@ // ReSharper disable SuggestUseVarKeywordEvident namespace GitHub { - /// - /// Represents the json array. - /// - [GeneratedCode("simple-json", "1.0.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonArray : List - { - /// - /// Initializes a new instance of the class. - /// - public JsonArray() { } - - /// - /// Initializes a new instance of the class. - /// - /// The capacity of the json array. - public JsonArray(int capacity) : base(capacity) { } - - /// - /// The json representation of the array. - /// - /// The json representation of the array. - public override string ToString() - { - return SimpleJson.SerializeObject(this) ?? string.Empty; - } - } - - /// - /// Represents the json object. - /// - [GeneratedCode("simple-json", "1.0.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonObject : -#if SIMPLE_JSON_DYNAMIC - DynamicObject, -#endif - IDictionary - { - /// - /// The internal member dictionary. - /// - private readonly Dictionary _members; - - /// - /// Initializes a new instance of . - /// - public JsonObject() - { - _members = new Dictionary(); - } - - /// - /// Initializes a new instance of . - /// - /// The implementation to use when comparing keys, or null to use the default for the type of the key. - public JsonObject(IEqualityComparer comparer) - { - _members = new Dictionary(comparer); - } - - /// - /// Gets the at the specified index. - /// - /// - public object this[int index] - { - get { return GetAtIndex(_members, index); } - } - - internal static object GetAtIndex(IDictionary obj, int index) - { - if (obj == null) - throw new ArgumentNullException("obj"); - if (index >= obj.Count) - throw new ArgumentOutOfRangeException("index"); - int i = 0; - foreach (KeyValuePair o in obj) - if (i++ == index) return o.Value; - return null; - } - - /// - /// Adds the specified key. - /// - /// The key. - /// The value. - public void Add(string key, object value) - { - _members.Add(key, value); - } - - /// - /// Determines whether the specified key contains key. - /// - /// The key. - /// - /// true if the specified key contains key; otherwise, false. - /// - public bool ContainsKey(string key) - { - return _members.ContainsKey(key); - } - - /// - /// Gets the keys. - /// - /// The keys. - public ICollection Keys - { - get { return _members.Keys; } - } - - /// - /// Removes the specified key. - /// - /// The key. - /// - public bool Remove(string key) - { - return _members.Remove(key); - } - - /// - /// Tries the get value. - /// - /// The key. - /// The value. - /// - public bool TryGetValue(string key, out object value) - { - return _members.TryGetValue(key, out value); - } - - /// - /// Gets the values. - /// - /// The values. - public ICollection Values - { - get { return _members.Values; } - } - - /// - /// Gets or sets the with the specified key. - /// - /// - public object this[string key] - { - get { return _members[key]; } - set { _members[key] = value; } - } - - /// - /// Adds the specified item. - /// - /// The item. - public void Add(KeyValuePair item) - { - _members.Add(item.Key, item.Value); - } - - /// - /// Clears this instance. - /// - public void Clear() - { - _members.Clear(); - } - - /// - /// Determines whether [contains] [the specified item]. - /// - /// The item. - /// - /// true if [contains] [the specified item]; otherwise, false. - /// - public bool Contains(KeyValuePair item) - { - return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; - } - - /// - /// Copies to. - /// - /// The array. - /// Index of the array. - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array == null) throw new ArgumentNullException("array"); - int num = Count; - foreach (KeyValuePair kvp in this) - { - array[arrayIndex++] = kvp; - if (--num <= 0) - return; - } - } - - /// - /// Gets the count. - /// - /// The count. - public int Count - { - get { return _members.Count; } - } - - /// - /// Gets a value indicating whether this instance is read only. - /// - /// - /// true if this instance is read only; otherwise, false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Removes the specified item. - /// - /// The item. - /// - public bool Remove(KeyValuePair item) - { - return _members.Remove(item.Key); - } - - /// - /// Gets the enumerator. - /// - /// - public IEnumerator> GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// - /// Returns a json that represents the current . - /// - /// - /// A json that represents the current . - /// - public override string ToString() - { - return SimpleJson.SerializeObject(this); - } - -#if SIMPLE_JSON_DYNAMIC - /// - /// Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. - /// - /// Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. - /// The result of the type conversion operation. - /// - /// Alwasy returns true. - /// - public override bool TryConvert(ConvertBinder binder, out object result) - { - // - if (binder == null) - throw new ArgumentNullException("binder"); - // - Type targetType = binder.Type; - - if ((targetType == typeof(IEnumerable)) || - (targetType == typeof(IEnumerable>)) || - (targetType == typeof(IDictionary)) || - (targetType == typeof(IDictionary))) - { - result = this; - return true; - } - - return base.TryConvert(binder, out result); - } - - /// - /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. - /// - /// Provides information about the deletion. - /// - /// Alwasy returns true. - /// - public override bool TryDeleteMember(DeleteMemberBinder binder) - { - // - if (binder == null) - throw new ArgumentNullException("binder"); - // - return _members.Remove(binder.Name); - } - - /// - /// Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. - /// - /// Provides information about the operation. - /// The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. - /// The result of the index operation. - /// - /// Alwasy returns true. - /// - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - if (indexes == null) throw new ArgumentNullException("indexes"); - if (indexes.Length == 1) - { - result = ((IDictionary)this)[(string)indexes[0]]; - return true; - } - result = null; - return true; - } - - /// - /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . - /// - /// Alwasy returns true. - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - object value; - if (_members.TryGetValue(binder.Name, out value)) - { - result = value; - return true; - } - result = null; - return true; - } - - /// - /// Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. - /// - /// Provides information about the operation. - /// The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. - /// The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. - /// - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) - { - if (indexes == null) throw new ArgumentNullException("indexes"); - if (indexes.Length == 1) - { - ((IDictionary)this)[(string)indexes[0]] = value; - return true; - } - return base.TrySetIndex(binder, indexes, value); - } - - /// - /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) - /// - public override bool TrySetMember(SetMemberBinder binder, object value) - { - // - if (binder == null) - throw new ArgumentNullException("binder"); - // - _members[binder.Name] = value; - return true; - } - - /// - /// Returns the enumeration of all dynamic member names. - /// - /// - /// A sequence that contains dynamic member names. - /// - public override IEnumerable GetDynamicMemberNames() - { - foreach (var key in Keys) - yield return key; - } -#endif - } -} - -namespace GitHub -{ - /// - /// This class encodes and decodes JSON strings. - /// Spec. details, see http://www.json.org/ - /// - /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). - /// All numbers are parsed to doubles. - /// - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - static class SimpleJson - { - private const int TOKEN_NONE = 0; - private const int TOKEN_CURLY_OPEN = 1; - private const int TOKEN_CURLY_CLOSE = 2; - private const int TOKEN_SQUARED_OPEN = 3; - private const int TOKEN_SQUARED_CLOSE = 4; - private const int TOKEN_COLON = 5; - private const int TOKEN_COMMA = 6; - private const int TOKEN_STRING = 7; - private const int TOKEN_NUMBER = 8; - private const int TOKEN_TRUE = 9; - private const int TOKEN_FALSE = 10; - private const int TOKEN_NULL = 11; - private const int BUILDER_CAPACITY = 2000; - - private static readonly char[] EscapeTable; - private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; - private static readonly string EscapeCharactersString = new string(EscapeCharacters); - - static SimpleJson() - { - EscapeTable = new char[93]; - EscapeTable['"'] = '"'; - EscapeTable['\\'] = '\\'; - EscapeTable['\b'] = 'b'; - EscapeTable['\f'] = 'f'; - EscapeTable['\n'] = 'n'; - EscapeTable['\r'] = 'r'; - EscapeTable['\t'] = 't'; - } - - /// - /// Parses the string json into a value - /// - /// A JSON string. - /// An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false - public static object DeserializeObject(string json) - { - object obj; - if (TryDeserializeObject(json, out obj)) - return obj; - throw new SerializationException("Invalid JSON string"); - } - - /// - /// Try parsing the json string into a value. - /// - /// - /// A JSON string. - /// - /// - /// The object. - /// - /// - /// Returns true if successfull otherwise false. - /// - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - public static bool TryDeserializeObject(string json, out object obj) - { - bool success = true; - if (json != null) - { - char[] charArray = json.ToCharArray(); - int index = 0; - obj = ParseValue(charArray, ref index, ref success); - } - else - obj = null; - - return success; - } - - public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) - { - object jsonObject = DeserializeObject(json); - return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) - ? jsonObject - : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); - } - - public static object DeserializeObject(string json, Type type) - { - return DeserializeObject(json, type, null); - } - - public static T DeserializeObject(string json, IJsonSerializerStrategy jsonSerializerStrategy) - { - return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); - } - - public static T DeserializeObject(string json) - { - return (T)DeserializeObject(json, typeof(T), null); - } - - /// - /// Converts a IDictionary<string,object> / IList<object> object into a JSON string - /// - /// A IDictionary<string,object> / IList<object> - /// Serializer strategy to use - /// A JSON encoded string, or null if object 'json' is not serializable - public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) - { - StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); - bool success = SerializeValue(jsonSerializerStrategy, json, builder); - return (success ? builder.ToString() : null); - } - - public static string SerializeObject(object json) - { - return SerializeObject(json, CurrentJsonSerializerStrategy); - } - - public static string EscapeToJavascriptString(string jsonString) - { - if (string.IsNullOrEmpty(jsonString)) - return jsonString; - - StringBuilder sb = new StringBuilder(); - char c; - - for (int i = 0; i < jsonString.Length; ) - { - c = jsonString[i++]; - - if (c == '\\') - { - int remainingLength = jsonString.Length - i; - if (remainingLength >= 2) - { - char lookahead = jsonString[i]; - if (lookahead == '\\') - { - sb.Append('\\'); - ++i; - } - else if (lookahead == '"') - { - sb.Append("\""); - ++i; - } - else if (lookahead == 't') - { - sb.Append('\t'); - ++i; - } - else if (lookahead == 'b') - { - sb.Append('\b'); - ++i; - } - else if (lookahead == 'n') - { - sb.Append('\n'); - ++i; - } - else if (lookahead == 'r') - { - sb.Append('\r'); - ++i; - } - } - } - else - { - sb.Append(c); - } - } - return sb.ToString(); - } - - static IDictionary ParseObject(char[] json, ref int index, ref bool success) - { - IDictionary table = new JsonObject(); - int token; - - // { - NextToken(json, ref index); - - bool done = false; - while (!done) - { - token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_CURLY_CLOSE) - { - NextToken(json, ref index); - return table; - } - else - { - // name - string name = ParseString(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - // : - token = NextToken(json, ref index); - if (token != TOKEN_COLON) - { - success = false; - return null; - } - // value - object value = ParseValue(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - table[name] = value; - } - } - return table; - } - - static JsonArray ParseArray(char[] json, ref int index, ref bool success) - { - JsonArray array = new JsonArray(); - - // [ - NextToken(json, ref index); - - bool done = false; - while (!done) - { - int token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_SQUARED_CLOSE) - { - NextToken(json, ref index); - break; - } - else - { - object value = ParseValue(json, ref index, ref success); - if (!success) - return null; - array.Add(value); - } - } - return array; - } - - static object ParseValue(char[] json, ref int index, ref bool success) - { - switch (LookAhead(json, index)) - { - case TOKEN_STRING: - return ParseString(json, ref index, ref success); - case TOKEN_NUMBER: - return ParseNumber(json, ref index, ref success); - case TOKEN_CURLY_OPEN: - return ParseObject(json, ref index, ref success); - case TOKEN_SQUARED_OPEN: - return ParseArray(json, ref index, ref success); - case TOKEN_TRUE: - NextToken(json, ref index); - return true; - case TOKEN_FALSE: - NextToken(json, ref index); - return false; - case TOKEN_NULL: - NextToken(json, ref index); - return null; - case TOKEN_NONE: - break; - } - success = false; - return null; - } - - static string ParseString(char[] json, ref int index, ref bool success) - { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); - char c; - - EatWhitespace(json, ref index); - - // " - c = json[index++]; - bool complete = false; - while (!complete) - { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') - { - complete = true; - break; - } - else if (c == '\\') - { - if (index == json.Length) - break; - c = json[index++]; - if (c == '"') - s.Append('"'); - else if (c == '\\') - s.Append('\\'); - else if (c == '/') - s.Append('/'); - else if (c == 'b') - s.Append('\b'); - else if (c == 'f') - s.Append('\f'); - else if (c == 'n') - s.Append('\n'); - else if (c == 'r') - s.Append('\r'); - else if (c == 't') - s.Append('\t'); - else if (c == 'u') - { - int remainingLength = json.Length - index; - if (remainingLength >= 4) - { - // parse the 32 bit hex into an integer codepoint - uint codePoint; - if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) - return ""; - - // convert the integer codepoint to a unicode char and add to string - if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate - { - index += 4; // skip 4 chars - remainingLength = json.Length - index; - if (remainingLength >= 6) - { - uint lowCodePoint; - if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) - { - if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate - { - s.Append((char)codePoint); - s.Append((char)lowCodePoint); - index += 6; // skip 6 chars - continue; - } - } - } - success = false; // invalid surrogate pair - return ""; - } - s.Append(ConvertFromUtf32((int)codePoint)); - // skip 4 chars - index += 4; - } - else - break; - } - } - else - s.Append(c); - } - if (!complete) - { - success = false; - return null; - } - return s.ToString(); - } - - private static string ConvertFromUtf32(int utf32) - { - // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm - if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); - if (0xD800 <= utf32 && utf32 <= 0xDFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); - if (utf32 < 0x10000) - return new string((char)utf32, 1); - utf32 -= 0x10000; - return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); - } - - static object ParseNumber(char[] json, ref int index, ref bool success) - { - EatWhitespace(json, ref index); - int lastIndex = GetLastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - object returnNumber; - string str = new string(json, index, charLength); - if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) - { - double number; - success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - else - { - long number; - success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - index = lastIndex + 1; - return returnNumber; - } - - static int GetLastIndexOfNumber(char[] json, int index) - { - int lastIndex; - for (lastIndex = index; lastIndex < json.Length; lastIndex++) - if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; - return lastIndex - 1; - } - - static void EatWhitespace(char[] json, ref int index) - { - for (; index < json.Length; index++) - if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; - } - - static int LookAhead(char[] json, int index) - { - int saveIndex = index; - return NextToken(json, ref saveIndex); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - static int NextToken(char[] json, ref int index) - { - EatWhitespace(json, ref index); - if (index == json.Length) - return TOKEN_NONE; - char c = json[index]; - index++; - switch (c) - { - case '{': - return TOKEN_CURLY_OPEN; - case '}': - return TOKEN_CURLY_CLOSE; - case '[': - return TOKEN_SQUARED_OPEN; - case ']': - return TOKEN_SQUARED_CLOSE; - case ',': - return TOKEN_COMMA; - case '"': - return TOKEN_STRING; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return TOKEN_NUMBER; - case ':': - return TOKEN_COLON; - } - index--; - int remainingLength = json.Length - index; - // false - if (remainingLength >= 5) - { - if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') - { - index += 5; - return TOKEN_FALSE; - } - } - // true - if (remainingLength >= 4) - { - if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') - { - index += 4; - return TOKEN_TRUE; - } - } - // null - if (remainingLength >= 4) - { - if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') - { - index += 4; - return TOKEN_NULL; - } - } - return TOKEN_NONE; - } - - static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) - { - bool success = true; - string stringValue = value as string; - if (stringValue != null) - success = SerializeString(stringValue, builder); - else - { - IDictionary dict = value as IDictionary; - if (dict != null) - { - success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); - } - else - { - IDictionary stringDictionary = value as IDictionary; - if (stringDictionary != null) - { - success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); - } - else - { - IEnumerable enumerableValue = value as IEnumerable; - if (enumerableValue != null) - success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); - else if (IsNumeric(value)) - success = SerializeNumber(value, builder); - else if (value is bool) - builder.Append((bool)value ? "true" : "false"); - else if (value == null) - builder.Append("null"); - else - { - object serializedObject; - success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); - if (success) - SerializeValue(jsonSerializerStrategy, serializedObject, builder); - } - } - } - } - return success; - } - - static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) - { - builder.Append("{"); - IEnumerator ke = keys.GetEnumerator(); - IEnumerator ve = values.GetEnumerator(); - bool first = true; - while (ke.MoveNext() && ve.MoveNext()) - { - object key = ke.Current; - object value = ve.Current; - if (!first) - builder.Append(","); - string stringKey = key as string; - if (stringKey != null) - SerializeString(stringKey, builder); - else - if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; - builder.Append(":"); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - first = false; - } - builder.Append("}"); - return true; - } - - static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) - { - builder.Append("["); - bool first = true; - foreach (object value in anArray) - { - if (!first) - builder.Append(","); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - first = false; - } - builder.Append("]"); - return true; - } - - static bool SerializeString(string aString, StringBuilder builder) - { - // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) - if (aString.IndexOfAny(EscapeCharacters) == -1) - { - builder.Append('"'); - builder.Append(aString); - builder.Append('"'); - - return true; - } - - builder.Append('"'); - int safeCharacterCount = 0; - char[] charArray = aString.ToCharArray(); - - for (int i = 0; i < charArray.Length; i++) - { - char c = charArray[i]; - - // Non ascii characters are fine, buffer them up and send them to the builder - // in larger chunks if possible. The escape table is a 1:1 translation table - // with \0 [default(char)] denoting a safe character. - if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) - { - safeCharacterCount++; - } - else - { - if (safeCharacterCount > 0) - { - builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); - safeCharacterCount = 0; - } - - builder.Append('\\'); - builder.Append(EscapeTable[c]); - } - } - - if (safeCharacterCount > 0) - { - builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); - } - - builder.Append('"'); - return true; - } - - static bool SerializeNumber(object number, StringBuilder builder) - { - if (number is long) - builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); - else if (number is ulong) - builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); - else if (number is int) - builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); - else if (number is uint) - builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); - else if (number is decimal) - builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); - else if (number is float) - builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); - else - builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); - return true; - } - - /// - /// Determines if a given object is numeric in any way - /// (can be integer, double, null, etc). - /// - static bool IsNumeric(object value) - { - if (value is sbyte) return true; - if (value is byte) return true; - if (value is short) return true; - if (value is ushort) return true; - if (value is int) return true; - if (value is uint) return true; - if (value is long) return true; - if (value is ulong) return true; - if (value is float) return true; - if (value is double) return true; - if (value is decimal) return true; - return false; - } - - private static IJsonSerializerStrategy _currentJsonSerializerStrategy; - public static IJsonSerializerStrategy CurrentJsonSerializerStrategy - { - get - { - return _currentJsonSerializerStrategy ?? - (_currentJsonSerializerStrategy = -#if SIMPLE_JSON_DATACONTRACT - DataContractJsonSerializerStrategy -#else - PocoJsonSerializerStrategy -#endif -); - } - set - { - _currentJsonSerializerStrategy = value; - } - } - - private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy - { - get - { - return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); - } - } - -#if SIMPLE_JSON_DATACONTRACT - - private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; - [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] - public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy - { - get - { - return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); - } - } - -#endif - } - - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - interface IJsonSerializerStrategy - { - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - bool TrySerializeNonPrimitiveObject(object input, out object output); - object DeserializeObject(object value, Type type); - } - - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class PocoJsonSerializerStrategy : IJsonSerializerStrategy - { - internal IDictionary ConstructorCache; - internal IDictionary> GetCache; - internal IDictionary>> SetCache; - - internal static readonly Type[] EmptyTypes = new Type[0]; - internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; - - private static readonly string[] Iso8601Format = new string[] - { - @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", - @"yyyy-MM-dd\THH:mm:ss\Z", - @"yyyy-MM-dd\THH:mm:ssK" - }; - - public PocoJsonSerializerStrategy() - { - ConstructorCache = new ReflectionUtils.ThreadSafeDictionary(ContructorDelegateFactory); - GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); - SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); - } - - protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) - { - return clrPropertyName; - } - - internal virtual ReflectionUtils.ConstructorDelegate ContructorDelegateFactory(Type key) - { - return ReflectionUtils.GetContructor(key, key.IsArray ? ArrayConstructorParameterTypes : EmptyTypes); - } - - internal virtual IDictionary GetterValueFactory(Type type) - { - IDictionary result = new Dictionary(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanRead) - { - MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (getMethod.IsStatic || !getMethod.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); - } - return result; - } - - internal virtual IDictionary> SetterValueFactory(Type type) - { - IDictionary> result = new Dictionary>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanWrite) - { - MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); - if (setMethod.IsStatic || !setMethod.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); - } - return result; - } - - public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) - { - return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - public virtual object DeserializeObject(object value, Type type) - { - if (type == null) throw new ArgumentNullException("type"); - string str = value as string; - - if (type == typeof (Guid) && string.IsNullOrEmpty(str)) - return default(Guid); - - if (value == null) - return null; - - object obj = null; - - if (str != null) - { - if (str.Length != 0) // We know it can't be null now. - { - if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) - return DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); - if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) - return DateTimeOffset.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); - if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) - return new Guid(str); - if (type == typeof(Uri)) - { - bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); - - Uri result; - if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) - return result; - - return null; - } - - if (type == typeof(string)) - return str; - - return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); - } - else - { - if (type == typeof(Guid)) - obj = default(Guid); - else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - obj = null; - else - obj = str; - } - // Empty string case - if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - return str; - } - else if (value is bool) - return value; - - bool valueIsLong = value is long; - bool valueIsDouble = value is double; - if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) - return value; - if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) - { - obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) - ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) - : value; - } - else - { - IDictionary objects = value as IDictionary; - if (objects != null) - { - IDictionary jsonObject = objects; - - if (ReflectionUtils.IsTypeDictionary(type)) - { - // if dictionary then - Type[] types = ReflectionUtils.GetGenericTypeArguments(type); - Type keyType = types[0]; - Type valueType = types[1]; - - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); - - IDictionary dict = (IDictionary)ConstructorCache[genericType](); - - foreach (KeyValuePair kvp in jsonObject) - dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); - - obj = dict; - } - else - { - if (type == typeof(object)) - obj = value; - else - { - obj = ConstructorCache[type](); - foreach (KeyValuePair> setter in SetCache[type]) - { - object jsonValue; - if (jsonObject.TryGetValue(setter.Key, out jsonValue)) - { - jsonValue = DeserializeObject(jsonValue, setter.Value.Key); - setter.Value.Value(obj, jsonValue); - } - } - } - } - } - else - { - IList valueAsList = value as IList; - if (valueAsList != null) - { - IList jsonObject = valueAsList; - IList list = null; - - if (type.IsArray) - { - list = (IList)ConstructorCache[type](jsonObject.Count); - int i = 0; - foreach (object o in jsonObject) - list[i++] = DeserializeObject(o, type.GetElementType()); - } - else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) - { - Type innerType = ReflectionUtils.GetGenericListElementType(type); - list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); - foreach (object o in jsonObject) - list.Add(DeserializeObject(o, innerType)); - } - obj = list; - } - } - return obj; - } - if (ReflectionUtils.IsNullableType(type)) - return ReflectionUtils.ToNullableType(obj, type); - return obj; - } - - protected virtual object SerializeEnum(Enum p) - { - return Convert.ToDouble(p, CultureInfo.InvariantCulture); - } - - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - protected virtual bool TrySerializeKnownTypes(object input, out object output) - { - bool returnValue = true; - if (input is DateTime) - output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); - else if (input is DateTimeOffset) - output = ((DateTimeOffset)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); - else if (input is Guid) - output = ((Guid)input).ToString("D"); - else if (input is Uri) - output = input.ToString(); - else - { - Enum inputEnum = input as Enum; - if (inputEnum != null) - output = SerializeEnum(inputEnum); - else - { - returnValue = false; - output = null; - } - } - return returnValue; - } - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - protected virtual bool TrySerializeUnknownTypes(object input, out object output) - { - if (input == null) throw new ArgumentNullException("input"); - output = null; - Type type = input.GetType(); - if (type.FullName == null) - return false; - IDictionary obj = new JsonObject(); - IDictionary getters = GetCache[type]; - foreach (KeyValuePair getter in getters) - { - if (getter.Value != null) - obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); - } - output = obj; - return true; - } - } - -#if SIMPLE_JSON_DATACONTRACT - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy - { - public DataContractJsonSerializerStrategy() - { - GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); - SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); - } - - internal override IDictionary GetterValueFactory(Type type) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - return base.GetterValueFactory(type); - string jsonKey; - IDictionary result = new Dictionary(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanRead) - { - MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) - result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) - result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); - } - return result; - } - - internal override IDictionary> SetterValueFactory(Type type) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - return base.SetterValueFactory(type); - string jsonKey; - IDictionary> result = new Dictionary>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanWrite) - { - MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); - if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) - result[jsonKey] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) - result[jsonKey] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); - } - // todo implement sorting for DATACONTRACT. - return result; - } - - private static bool CanAdd(MemberInfo info, out string jsonKey) - { - jsonKey = null; - if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) - return false; - DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); - if (dataMemberAttribute == null) - return false; - jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; - return true; - } - } - -#endif - namespace Reflection { // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules From a5a1c50c91de641b950f1a056b312dc5f5a61a44 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 19:59:03 +0100 Subject: [PATCH 10/15] Fix names of tests --- .../GitHub.VisualStudio/Services/UsageTrackerTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs index 582043ee1f..e665984869 100644 --- a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs +++ b/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs @@ -238,7 +238,7 @@ public async Task UsageServiceWritesAllTheDataCorrectly() } [Test] - public async Task MetricserviceSendsDailyData() + public async Task MetricsServiceSendsDailyData() { var model = CreateUsageModel(); var serviceProvider = CreateServiceProvider(); @@ -264,7 +264,7 @@ public async Task MetricserviceSendsDailyData() } [Test] - public async Task MetricserviceSendsWeeklyData() + public async Task MetricsServiceSendsWeeklyData() { var model = CreateUsageModel(); var serviceProvider = CreateServiceProvider(); @@ -291,7 +291,7 @@ public async Task MetricserviceSendsWeeklyData() } [Test] - public async Task MetricserviceSendsMonthlyData() + public async Task MetricsServiceSendsMonthlyData() { var model = CreateUsageModel(); var serviceProvider = CreateServiceProvider(); @@ -318,7 +318,7 @@ public async Task MetricserviceSendsMonthlyData() ReflectionUtils.GetProperties(result.GetType()).Select(x => new { x.Name, Value = x.GetValue(result) })); } - private static UsageModel CreateUsageModel() + static UsageModel CreateUsageModel() { var count = 1; // UsageModel is a struct so we have to force box it to be able to set values on it From ac66dec4aee667c54b1f7530e867778606f689f8 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Wed, 7 Feb 2018 20:38:04 +0100 Subject: [PATCH 11/15] Fix IsSameWeek and IsSameMonth. Add tests for that. --- .../Services/UsageService.cs | 4 +- .../{UsageTrackerTests.cs => MetricsTests.cs} | 88 ++++++++++++++++++- test/UnitTests/UnitTests.csproj | 2 +- 3 files changed, 89 insertions(+), 5 deletions(-) rename test/UnitTests/GitHub.VisualStudio/Services/{UsageTrackerTests.cs => MetricsTests.cs} (81%) diff --git a/src/GitHub.VisualStudio/Services/UsageService.cs b/src/GitHub.VisualStudio/Services/UsageService.cs index 755cee8db6..36c4459632 100644 --- a/src/GitHub.VisualStudio/Services/UsageService.cs +++ b/src/GitHub.VisualStudio/Services/UsageService.cs @@ -32,12 +32,12 @@ public bool IsSameDay(DateTimeOffset lastUpdated) public bool IsSameWeek(DateTimeOffset lastUpdated) { - return GetIso8601WeekOfYear(lastUpdated) == GetIso8601WeekOfYear(DateTimeOffset.Now) && lastUpdated.Year != DateTimeOffset.Now.Year; + return GetIso8601WeekOfYear(lastUpdated) == GetIso8601WeekOfYear(DateTimeOffset.Now) && lastUpdated.Year == DateTimeOffset.Now.Year; } public bool IsSameMonth(DateTimeOffset lastUpdated) { - return lastUpdated.Month == DateTimeOffset.Now.Month; + return lastUpdated.Month == DateTimeOffset.Now.Month && lastUpdated.Year == DateTimeOffset.Now.Year; } public IDisposable StartTimer(Func callback, TimeSpan dueTime, TimeSpan period) diff --git a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs b/test/UnitTests/GitHub.VisualStudio/Services/MetricsTests.cs similarity index 81% rename from test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs rename to test/UnitTests/GitHub.VisualStudio/Services/MetricsTests.cs index e665984869..773b370871 100644 --- a/test/UnitTests/GitHub.VisualStudio/Services/UsageTrackerTests.cs +++ b/test/UnitTests/GitHub.VisualStudio/Services/MetricsTests.cs @@ -7,11 +7,9 @@ using GitHub.Settings; using NSubstitute; using NUnit.Framework; -using System.Reflection; using System.Linq; using System.Globalization; using GitHub.Reflection; -using GitHub; namespace MetricsTests { @@ -418,4 +416,90 @@ static IUsageService CreateUsageService( return result; } } + + public class UsageServiceTests : TestBaseClass + { + [Test] + public void IsSameDayWorks() + { + var usageService = new UsageService(Substitute.For()); + var now = DateTimeOffset.Now; + Assert.True(usageService.IsSameDay(now)); + Assert.True(usageService.IsSameDay(new DateTimeOffset(now.Year, now.Month, now.Day, 0, 0, 0, TimeSpan.Zero))); + Assert.False(usageService.IsSameDay(new DateTimeOffset(now.Year, now.Month, now.Day+1, 0, 0, 0, TimeSpan.Zero))); + Assert.False(usageService.IsSameDay(new DateTimeOffset(now.Year, now.Month, now.Day-1, 0, 0, 0, TimeSpan.Zero))); + Assert.True(usageService.IsSameDay(new DateTimeOffset(now.Year, now.Month, now.Day, 10, 3, 1, TimeSpan.Zero))); + Assert.False(usageService.IsSameDay(new DateTimeOffset(now.Year, now.Month, now.Day+1, 10, 3, 1, TimeSpan.Zero))); + Assert.False(usageService.IsSameDay(new DateTimeOffset(now.Year, now.Month, now.Day-1, 10, 3, 1, TimeSpan.Zero))); + } + + [Test] + public void IsSameWeekWorks() + { + var usageService = new UsageService(Substitute.For()); + var now = DateTimeOffset.Now; + + Assert.True(usageService.IsSameWeek(now)); + var nowWeek = GetIso8601WeekOfYear(now); + + DateTimeOffset nextWeek = now; + for (int i = 1; i < 8; i++) + { + nextWeek = nextWeek.AddDays(1); + var week = GetIso8601WeekOfYear(nextWeek); + Assert.AreEqual(week == nowWeek, usageService.IsSameWeek(nextWeek)); + } + + DateTimeOffset prevWeek = now; + for (int i = 1; i < 8; i++) + { + prevWeek = prevWeek.AddDays(-1); + var week = GetIso8601WeekOfYear(prevWeek); + Assert.AreEqual(week == nowWeek, usageService.IsSameWeek(prevWeek)); + } + + Assert.False(usageService.IsSameWeek(now.AddYears(1))); + } + + [Test] + public void IsSameMonthWorks() + { + var usageService = new UsageService(Substitute.For()); + var now = DateTimeOffset.Now; + + Assert.True(usageService.IsSameMonth(now)); + + DateTimeOffset nextMonth = now; + for (int i = 1; i < 40; i++) + { + nextMonth = nextMonth.AddDays(1); + Assert.AreEqual(nextMonth.Month == now.Month, usageService.IsSameMonth(nextMonth)); + } + + DateTimeOffset prevMonth = now; + for (int i = 1; i < 40; i++) + { + prevMonth = prevMonth.AddDays(-1); + Assert.AreEqual(prevMonth.Month == now.Month, usageService.IsSameMonth(prevMonth)); + } + + Assert.False(usageService.IsSameMonth(now.AddYears(1))); + } + + // http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx + static int GetIso8601WeekOfYear(DateTimeOffset time) + { + // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll + // be the same week# as whatever Thursday, Friday or Saturday are, + // and we always get those right + DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time.UtcDateTime); + if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) + { + time = time.AddDays(3); + } + + // Return the week of our adjusted day + return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time.UtcDateTime, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); + } + } } diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index eb05758fe7..79a7c78553 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -268,7 +268,7 @@ - + From e165776a4ab686eaa528c7a3cf39b875e660fa6b Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 8 Feb 2018 14:32:13 +0100 Subject: [PATCH 12/15] Add support for deserializing structs with SimpleJson --- src/GitHub.Exports/SimpleJson.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/GitHub.Exports/SimpleJson.cs b/src/GitHub.Exports/SimpleJson.cs index f59c702bc8..82360321e7 100644 --- a/src/GitHub.Exports/SimpleJson.cs +++ b/src/GitHub.Exports/SimpleJson.cs @@ -67,6 +67,7 @@ using System.Runtime.Serialization; using System.Text; using GitHub.Reflection; +using System.Diagnostics; // ReSharper disable LoopCanBeConvertedToQuery // ReSharper disable RedundantExplicitArrayCreation @@ -1838,7 +1839,13 @@ public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo con public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) { ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); - return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); + // if it's a value type (i.e., struct), it won't have a default constructor, so use Activator instead + return constructorInfo == null ? (type.IsValueType ? GetConstructorForValueType(type) : null) : GetConstructorByReflection(constructorInfo); + } + + static ConstructorDelegate GetConstructorForValueType(Type type) + { + return delegate (object[] args) { return Activator.CreateInstance(type); }; } #if !SIMPLE_JSON_NO_LINQ_EXPRESSION @@ -1865,7 +1872,8 @@ public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo con public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) { ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); - return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); + // if it's a value type (i.e., struct), it won't have a default constructor, so use Activator instead + return constructorInfo == null ? (type.IsValueType ? GetConstructorForValueType(type) : null) : GetConstructorByExpression(constructorInfo); } #endif @@ -1925,6 +1933,9 @@ public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) #if SIMPLE_JSON_NO_LINQ_EXPRESSION return GetSetMethodByReflection(propertyInfo); #else + // if it's a struct, we want to use reflection, as linq expressions modify copies of the object and not the real thing + if (propertyInfo.DeclaringType.IsValueType) + return GetSetMethodByReflection(propertyInfo); return GetSetMethodByExpression(propertyInfo); #endif } @@ -1934,6 +1945,9 @@ public static SetDelegate GetSetMethod(FieldInfo fieldInfo) #if SIMPLE_JSON_NO_LINQ_EXPRESSION return GetSetMethodByReflection(fieldInfo); #else + // if it's a struct, we want to use reflection, as linq expressions modify copies of the object and not the real thing + if (fieldInfo.DeclaringType.IsValueType) + return GetSetMethodByReflection(fieldInfo); return GetSetMethodByExpression(fieldInfo); #endif } From a8ac83a86aba2ab65dbbd66d5706c3db3a986a26 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 8 Feb 2018 14:41:13 +0100 Subject: [PATCH 13/15] Log errors --- src/GitHub.Logging/Logging/LogManager.cs | 4 ++++ src/GitHub.VisualStudio/Services/UsageService.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/GitHub.Logging/Logging/LogManager.cs b/src/GitHub.Logging/Logging/LogManager.cs index 121ea56aad..1ae3469b0f 100644 --- a/src/GitHub.Logging/Logging/LogManager.cs +++ b/src/GitHub.Logging/Logging/LogManager.cs @@ -20,7 +20,11 @@ static Logger CreateLogger() return new LoggerConfiguration() .Enrich.WithThreadId() +#if DEBUG + .MinimumLevel.Debug() +#else .MinimumLevel.Information() +#endif .WriteTo.File(logPath, fileSizeLimitBytes: null, outputTemplate: outputTemplate) diff --git a/src/GitHub.VisualStudio/Services/UsageService.cs b/src/GitHub.VisualStudio/Services/UsageService.cs index 36c4459632..110aa11bb8 100644 --- a/src/GitHub.VisualStudio/Services/UsageService.cs +++ b/src/GitHub.VisualStudio/Services/UsageService.cs @@ -8,12 +8,15 @@ using GitHub.Helpers; using GitHub.Models; using Task = System.Threading.Tasks.Task; +using GitHub.Logging; +using Serilog; namespace GitHub.Services { [Export(typeof(IUsageService))] public class UsageService : IUsageService { + static readonly ILogger log = LogManager.ForContext(); const string StoreFileName = "ghfvs.usage"; static readonly Calendar cal = CultureInfo.InvariantCulture.Calendar; readonly IGitHubServiceProvider serviceProvider; @@ -65,8 +68,9 @@ public async Task ReadLocalData() SimpleJson.DeserializeObject(json) : new UsageData { Model = new UsageModel() }; } - catch + catch(Exception ex) { + log.Error(ex, "Error deserializing usage"); return new UsageData { Model = new UsageModel() }; } } @@ -79,9 +83,9 @@ public async Task WriteLocalData(UsageData data) var json = SimpleJson.SerializeObject(data); await WriteAllTextAsync(storePath, json); } - catch + catch(Exception ex) { - // log.Warn("Failed to write usage data", ex); + log.Error(ex,"Failed to write usage data"); } } From 542966a5ddd43455b402a0d3afb82e87d54ea285 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Thu, 8 Feb 2018 14:43:04 +0100 Subject: [PATCH 14/15] Log things at appropriate levels --- src/GitHub.App/Api/ApiClient.cs | 2 +- src/GitHub.App/Services/TeamExplorerContext.cs | 10 +++++----- src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs | 2 +- .../Connect/GitHubConnectSection.cs | 8 ++------ src/GitHub.TeamFoundation.14/Services/VSGitExt.cs | 8 ++++---- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs index 8608fb1a9c..5792aacb08 100644 --- a/src/GitHub.App/Api/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -157,7 +157,7 @@ static string GetMachineNameSafe() } catch (Exception e) { - log.Information(e, "Failed to retrieve host name using `DNS.GetHostName`"); + log.Warning(e, "Failed to retrieve host name using `DNS.GetHostName`"); try { return Environment.MachineName; diff --git a/src/GitHub.App/Services/TeamExplorerContext.cs b/src/GitHub.App/Services/TeamExplorerContext.cs index 0187c1e7e5..b6a34a4972 100644 --- a/src/GitHub.App/Services/TeamExplorerContext.cs +++ b/src/GitHub.App/Services/TeamExplorerContext.cs @@ -65,7 +65,7 @@ void Refresh() { // Ignore when ActiveRepositories is empty and solution hasn't changed. // https://github.com/github/VisualStudio/issues/1421 - log.Information("Ignoring no ActiveRepository when solution hasn't changed"); + log.Debug("Ignoring no ActiveRepository when solution hasn't changed"); } else { @@ -76,22 +76,22 @@ void Refresh() if (newRepositoryPath != repositoryPath) { - log.Information("Fire PropertyChanged event for ActiveRepository"); + log.Debug("Fire PropertyChanged event for ActiveRepository"); ActiveRepository = repo; } else if (newBranchName != branchName) { - log.Information("Fire StatusChanged event when BranchName changes for ActiveRepository"); + log.Debug("Fire StatusChanged event when BranchName changes for ActiveRepository"); StatusChanged?.Invoke(this, EventArgs.Empty); } else if (newHeadSha != headSha) { - log.Information("Fire StatusChanged event when HeadSha changes for ActiveRepository"); + log.Debug("Fire StatusChanged event when HeadSha changes for ActiveRepository"); StatusChanged?.Invoke(this, EventArgs.Empty); } else if (newTrackedSha != trackedSha) { - log.Information("Fire StatusChanged event when TrackedSha changes for ActiveRepository"); + log.Debug("Fire StatusChanged event when TrackedSha changes for ActiveRepository"); StatusChanged?.Invoke(this, EventArgs.Empty); } diff --git a/src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs index fd98f22f81..b0948176c4 100644 --- a/src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs +++ b/src/GitHub.App/ViewModels/Dialog/LoginTabViewModel.cs @@ -200,7 +200,7 @@ void HandleError(Exception ex) if (ex.IsCriticalException()) return; - log.Information(ex, "Error logging into '{BaseUri}' as '{UsernameOrEmail}'", BaseUri, UsernameOrEmail); + log.Error(ex, "Error logging into '{BaseUri}' as '{UsernameOrEmail}'", BaseUri, UsernameOrEmail); if (ex is Octokit.ForbiddenException) { Error = new UserError(Resources.LoginFailedForbiddenMessage, ex.Message); diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs index 55227576ff..d97468a6c2 100644 --- a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs @@ -360,9 +360,7 @@ void ShowNotification(ILocalRepositoryModel newrepo, string msg) } }) ); -#if DEBUG - log.Information("Notification"); -#endif + log.Debug("Notification"); } async Task RefreshRepositories() @@ -496,12 +494,10 @@ void TrackState(object sender, PropertyChangedEventArgs e) { if (machine.PermittedTriggers.Contains(e.PropertyName)) { -#if DEBUG - log.Information("{PropertyName} title:{Title} busy:{IsBusy}", + log.Debug("{PropertyName} title:{Title} busy:{IsBusy}", e.PropertyName, ((ITeamExplorerSection)sender).Title, ((ITeamExplorerSection)sender).IsBusy); -#endif machine.Fire(e.PropertyName); } } diff --git a/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs b/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs index 1ffed5c502..9903f194e8 100644 --- a/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs +++ b/src/GitHub.TeamFoundation.14/Services/VSGitExt.cs @@ -55,7 +55,7 @@ public VSGitExt(IGitHubServiceProvider serviceProvider, IVSUIContextFactory fact { // If we're not in the UIContext or TryInitialize fails, have another go when the UIContext changes. context.UIContextChanged += ContextChanged; - log.Information("VSGitExt will be initialized later"); + log.Debug("VSGitExt will be initialized later"); InitializeTask = Task.CompletedTask; } } @@ -69,7 +69,7 @@ void ContextChanged(object sender, VSUIContextChangedEventArgs e) // Refresh ActiveRepositories on background thread so we don't delay UI context change. InitializeTask = Task.Run(() => RefreshActiveRepositories()); context.UIContextChanged -= ContextChanged; - log.Information("Initialized VSGitExt on UIContextChanged"); + log.Debug("Initialized VSGitExt on UIContextChanged"); } } @@ -86,11 +86,11 @@ bool TryInitialize() } }; - log.Information("Found IGitExt service and initialized VSGitExt"); + log.Debug("Found IGitExt service and initialized VSGitExt"); return true; } - log.Information("Couldn't find IGitExt service"); + log.Error("Couldn't find IGitExt service"); return false; } From 3be40a02b6947a91e92ba7fc41571c7e717da2d2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 8 Feb 2018 18:20:53 +0100 Subject: [PATCH 15/15] Fix SimpleApiClient.IsEnterprise(). Previous unless you had called `GetRepository` on a `SimpleApiClient` instance, `IsEnterprise()` simply returned `false` without checking. Make sure we actually perform the check. Fixes #1473 --- src/GitHub.Api/SimpleApiClient.cs | 11 ++++++++--- .../ViewModels/GitHubPane/GitHubPaneViewModel.cs | 2 +- src/GitHub.Exports/Api/ISimpleApiClient.cs | 2 +- .../Base/TeamExplorerItemBase.cs | 2 +- src/GitHub.VisualStudio/Menus/MenuBase.cs | 2 +- test/UnitTests/GitHub.Api/SimpleApiClientTests.cs | 6 +++--- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/GitHub.Api/SimpleApiClient.cs b/src/GitHub.Api/SimpleApiClient.cs index 68f9f827a4..5ae5de86c9 100644 --- a/src/GitHub.Api/SimpleApiClient.cs +++ b/src/GitHub.Api/SimpleApiClient.cs @@ -72,7 +72,7 @@ async Task GetRepositoryInternal() if (repo != null) { hasWiki = await HasWikiInternal(repo); - isEnterprise = await IsEnterpriseInternal(); + isEnterprise = isEnterprise ?? await IsEnterpriseInternal(); repositoryCache = repo; } owner = ownerLogin; @@ -99,9 +99,14 @@ public bool HasWiki() return hasWiki.HasValue && hasWiki.Value; } - public bool IsEnterprise() + public async Task IsEnterprise() { - return isEnterprise.HasValue && isEnterprise.Value; + if (!isEnterprise.HasValue) + { + isEnterprise = await IsEnterpriseInternal(); + } + + return isEnterprise ?? false; } async Task HasWikiInternal(Repository repo) diff --git a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs index 1125b6c211..813254202d 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs @@ -366,7 +366,7 @@ async Task UpdateContent(ILocalRepositoryModel repository) var repositoryUrl = repository.CloneUrl.ToRepositoryUrl(); var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl); var client = await apiClientFactory.Create(repository.CloneUrl); - var isEnterprise = isDotCom ? false : client.IsEnterprise(); + var isEnterprise = isDotCom ? false : await client.IsEnterprise(); if ((isDotCom || isEnterprise) && await IsValidRepository(client)) { diff --git a/src/GitHub.Exports/Api/ISimpleApiClient.cs b/src/GitHub.Exports/Api/ISimpleApiClient.cs index ba0417caea..28004d6951 100644 --- a/src/GitHub.Exports/Api/ISimpleApiClient.cs +++ b/src/GitHub.Exports/Api/ISimpleApiClient.cs @@ -11,7 +11,7 @@ public interface ISimpleApiClient UriString OriginalUrl { get; } Task GetRepository(); bool HasWiki(); - bool IsEnterprise(); + Task IsEnterprise(); bool IsAuthenticated(); } } diff --git a/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs b/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs index 1bc493730a..1990867bde 100644 --- a/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs +++ b/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs @@ -126,7 +126,7 @@ protected async Task GetRepositoryOrigin() { var repo = await SimpleApiClient.GetRepository(); - if ((repo.FullName == ActiveRepoName || repo.Id == 0) && SimpleApiClient.IsEnterprise()) + if ((repo.FullName == ActiveRepoName || repo.Id == 0) && await SimpleApiClient.IsEnterprise()) { return RepositoryOrigin.Enterprise; } diff --git a/src/GitHub.VisualStudio/Menus/MenuBase.cs b/src/GitHub.VisualStudio/Menus/MenuBase.cs index 91ed4ca118..bafb619d4f 100644 --- a/src/GitHub.VisualStudio/Menus/MenuBase.cs +++ b/src/GitHub.VisualStudio/Menus/MenuBase.cs @@ -123,7 +123,7 @@ protected async Task IsGitHubRepo() { var repo = await SimpleApiClient.GetRepository(); var activeRepoFullName = ActiveRepo.Owner + '/' + ActiveRepo.Name; - return (repo.FullName == activeRepoFullName || repo.Id == 0) && SimpleApiClient.IsEnterprise(); + return (repo.FullName == activeRepoFullName || repo.Id == 0) && await SimpleApiClient.IsEnterprise(); } return isdotcom; } diff --git a/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs b/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs index 694c74ea42..980da768df 100644 --- a/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs +++ b/test/UnitTests/GitHub.Api/SimpleApiClientTests.cs @@ -132,10 +132,10 @@ public async Task ReturnsTrueWhenEnterpriseProbeReturnsOk(EnterpriseProbeResult new Lazy(() => wikiProbe)); await client.GetRepository(); - var result = client.IsEnterprise(); + var result = await client.IsEnterprise(); Assert.That(expected, Is.EqualTo(result)); - Assert.That(expected, Is.EqualTo(client.IsEnterprise())); + Assert.That(expected, Is.EqualTo(await client.IsEnterprise())); } [Test] @@ -151,7 +151,7 @@ public void ReturnsFalseWhenWeHaveNotRequestedRepository() new Lazy(() => enterpriseProbe), new Lazy(() => wikiProbe)); - var result = client.IsEnterprise(); + var result = client.IsEnterprise().Result; Assert.False(result); }