Commit 38421b7
Changed files (61)
product
database
db4o
transactions
Presentation
product/database/db4o/Spiking/db40_spike_specs.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using System.IO;
+using Db4objects.Db4o;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.db4o.Spiking
+{
+ [Concern(typeof (Db4oFactory))]
+ public class when_opening_an_existing_database_ : concerns
+ {
+ before_each_observation be = () => {};
+
+ context c = () =>
+ {
+ original = new TestObject(88, "mo");
+ the_database_file = Path.GetTempFileName();
+ var configuration = Db4oFactory.NewConfiguration();
+ configuration.LockDatabaseFile(false);
+ database = Db4oFactory.OpenFile(configuration, the_database_file);
+ };
+
+ because b = () =>
+ {
+ database.Store(original);
+ database.Close();
+ database.Dispose();
+ database = Db4oFactory.OpenFile(the_database_file);
+ results = database.Query<ITestObject>().databind();
+ };
+
+ it should_be_able_to_load_the_original_contents = () => results.should_contain(original);
+
+ it they_should_be_equal = () => new TestObject(99, "gretzky").Equals(new TestObject(99, "gretzky"));
+
+ it should_only_contain_the_original_item = () => results.Count.should_be_equal_to(1);
+
+ after_each_observation ae = () =>
+ {
+ database.Close();
+ database.Dispose();
+ };
+
+ static ITestObject original;
+ static string the_database_file;
+ static IList<ITestObject> results;
+ static IObjectContainer database;
+ }
+
+ public interface ITestObject
+ {
+ int Id { get; }
+ string Name { get; }
+ }
+
+ public class TestObject : ITestObject
+ {
+ public TestObject(int id, string name)
+ {
+ Id = id;
+ Name = name;
+ }
+
+ public int Id { get; private set; }
+ public string Name { get; private set; }
+
+ public bool Equals(TestObject obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj.Id == Id && Equals(obj.Name, Name);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (TestObject)) return false;
+ return Equals((TestObject) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Id*397) ^ (Name != null ? Name.GetHashCode() : 0);
+ }
+ }
+ }
+}
\ No newline at end of file
product/database/db4o/ConfigureDatabaseStep.cs
@@ -0,0 +1,18 @@
+using Db4objects.Db4o.Config;
+using gorilla.commons.utility;
+
+namespace momoney.database.db4o
+{
+ public interface IConfigureDatabaseStep : Configuration<IConfiguration> {}
+
+ public class ConfigureDatabaseStep : IConfigureDatabaseStep
+ {
+ public void configure(IConfiguration item)
+ {
+ item.LockDatabaseFile(false);
+ //item.UpdateDepth(10);
+ //item.WeakReferences(true);
+ //item.AutomaticShutDown(true);
+ }
+ }
+}
\ No newline at end of file
product/database/db4o/ConfigureObjectContainerStep.cs
@@ -0,0 +1,20 @@
+using Db4objects.Db4o;
+using Db4objects.Db4o.Events;
+using Gorilla.Commons.Infrastructure.Logging;
+using gorilla.commons.utility;
+
+namespace momoney.database.db4o
+{
+ public interface IConfigureObjectContainerStep : Configuration<IObjectContainer> {}
+
+ public class ConfigureObjectContainerStep : IConfigureObjectContainerStep
+ {
+ public void configure(IObjectContainer item)
+ {
+ var registry = EventRegistryFactory.ForObjectContainer(item);
+ registry.ClassRegistered += (sender, args) => this.log().debug("class registered: {0}", args.ClassMetadata());
+ registry.Instantiated += (sender, args) => this.log().debug("class instantiated: {0}", args.Object.GetType().Name);
+ registry.Committed += (sender, args) => this.log().debug("added: {0}, updated: {1}, deleted: {2}", args.Added, args.Updated, args.Deleted);
+ }
+ }
+}
\ No newline at end of file
product/database/db4o/ConnectionFactory.cs
@@ -0,0 +1,34 @@
+using Db4objects.Db4o;
+using Db4objects.Db4o.Config;
+using Gorilla.Commons.Infrastructure.FileSystem;
+using gorilla.commons.utility;
+using momoney.database.transactions;
+
+namespace momoney.database.db4o
+{
+ public class ConnectionFactory : IConnectionFactory
+ {
+ readonly IConfigureDatabaseStep setup;
+ readonly IConfigureObjectContainerStep setup_container;
+
+ public ConnectionFactory(IConfigureDatabaseStep setup, IConfigureObjectContainerStep setup_container)
+ {
+ this.setup = setup;
+ this.setup_container = setup_container;
+ }
+
+ public IDatabaseConnection open_connection_to(File the_path_to_the_database_file)
+ {
+ var configuration = Db4oFactory.NewConfiguration();
+ setup.configure(configuration);
+ return new DatabaseConnection(get_container(the_path_to_the_database_file, configuration));
+ }
+
+ IObjectContainer get_container(File the_path_to_the_database_file, IConfiguration configuration)
+ {
+ return Db4oFactory
+ .OpenFile(configuration, the_path_to_the_database_file.path)
+ .and_configure_with(setup_container);
+ }
+ }
+}
\ No newline at end of file
product/database/db4o/DatabaseConnection.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using Db4objects.Db4o;
+using momoney.database.transactions;
+
+namespace momoney.database.db4o
+{
+ public class DatabaseConnection : IDatabaseConnection
+ {
+ readonly IObjectContainer container;
+
+ public DatabaseConnection(IObjectContainer container)
+ {
+ this.container = container;
+ }
+
+ public void Dispose()
+ {
+ container.Close();
+ container.Dispose();
+ }
+
+ public IEnumerable<T> query<T>()
+ {
+ return container.Query<T>();
+ }
+
+ public IEnumerable<T> query<T>(Predicate<T> predicate)
+ {
+ return container.Query(predicate);
+ }
+
+ public void delete<T>(T entity)
+ {
+ container.Delete(entity);
+ }
+
+ public void commit()
+ {
+ container.Commit();
+ }
+
+ public void store<T>(T entity)
+ {
+ container.Store(entity);
+ }
+ }
+}
\ No newline at end of file
product/database/repositories/AccountHolderRepository.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using momoney.database.transactions;
+using MoMoney.Domain.accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+ public class AccountHolderRepository : IAccountHolderRepository
+ {
+ readonly ISession session;
+
+ public AccountHolderRepository(ISession session)
+ {
+ this.session = session;
+ }
+
+ public IEnumerable<IAccountHolder> all()
+ {
+ return session.all<IAccountHolder>();
+ }
+
+ public void save(IAccountHolder account_holder)
+ {
+ session.save(account_holder);
+ }
+ }
+}
\ No newline at end of file
product/database/repositories/BillRepository.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using momoney.database.transactions;
+using MoMoney.Domain.Accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+ public class BillRepository : IBillRepository
+ {
+ readonly ISession session;
+
+ public BillRepository(ISession session)
+ {
+ this.session = session;
+ }
+
+ public IEnumerable<IBill> all()
+ {
+ return session.all<IBill>();
+ }
+ }
+}
\ No newline at end of file
product/database/repositories/CompanyRepository.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+using momoney.database.transactions;
+using MoMoney.Domain.Accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+ public class CompanyRepository : ICompanyRepository
+ {
+ readonly ISession session;
+
+ public CompanyRepository(ISession session)
+ {
+ this.session = session;
+ }
+
+ public IEnumerable<ICompany> all()
+ {
+ return session.all<ICompany>();
+ }
+
+ public ICompany find_company_named(string name)
+ {
+ return session
+ .all<ICompany>()
+ .SingleOrDefault(x => x.name.is_equal_to_ignoring_case(name));
+ }
+
+ public ICompany find_company_by(Guid id)
+ {
+ return session.all<ICompany>().SingleOrDefault(x => x.id.Equals(id));
+ }
+
+ public void save(ICompany company)
+ {
+ session.save(company);
+ }
+ }
+}
\ No newline at end of file
product/database/repositories/IncomeRepository.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using momoney.database.transactions;
+using MoMoney.Domain.Accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+ public class IncomeRepository : IIncomeRepository
+ {
+ readonly ISession session;
+
+ public IncomeRepository(ISession session)
+ {
+ this.session = session;
+ }
+
+ public IEnumerable<IIncome> all()
+ {
+ return session.all<IIncome>();
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/ChangeTracker.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class ChangeTracker<T> : IChangeTracker<T> where T : Identifiable<Guid>
+ {
+ readonly ITrackerEntryMapper<T> mapper;
+ readonly IStatementRegistry registry;
+ readonly IList<ITrackerEntry<T>> items;
+ readonly IList<T> to_be_deleted;
+
+ public ChangeTracker(ITrackerEntryMapper<T> mapper, IStatementRegistry registry)
+ {
+ this.mapper = mapper;
+ this.registry = registry;
+ items = new List<ITrackerEntry<T>>();
+ to_be_deleted = new List<T>();
+ }
+
+ public void register(T entity)
+ {
+ items.Add(mapper.map_from(entity));
+ }
+
+ public void delete(T entity)
+ {
+ to_be_deleted.Add(entity);
+ }
+
+ public void commit_to(IDatabase database)
+ {
+ items.each(x => commit(x, database));
+ to_be_deleted.each(x => database.apply(registry.prepare_command_for(x)));
+ }
+
+ public bool is_dirty()
+ {
+ return items.Count(x => x.has_changes()) > 0 || to_be_deleted.Count > 0;
+ }
+
+ public void Dispose()
+ {
+ items.Clear();
+ }
+
+ void commit(ITrackerEntry<T> entry, IDatabase database)
+ {
+ if (entry.has_changes()) database.apply(registry.prepare_command_for(entry.current));
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/ChangeTrackerFactory.cs
@@ -0,0 +1,23 @@
+using System;
+using Gorilla.Commons.Infrastructure.Container;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class ChangeTrackerFactory : IChangeTrackerFactory
+ {
+ readonly IStatementRegistry statement_registry;
+ readonly DependencyRegistry registry;
+
+ public ChangeTrackerFactory(IStatementRegistry statement_registry, DependencyRegistry registry)
+ {
+ this.statement_registry = statement_registry;
+ this.registry = registry;
+ }
+
+ public IChangeTracker<T> create_for<T>() where T : Identifiable<Guid>
+ {
+ return new ChangeTracker<T>(registry.get_a<ITrackerEntryMapper<T>>(), statement_registry);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/ChangeTrackerFactorySpecs.cs
@@ -0,0 +1,22 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class ChangeTrackerFactorySpecs {}
+
+ [Concern(typeof (ChangeTrackerFactory))]
+ public class when_creating_a_change_tracker_for_an_item : concerns_for<IChangeTrackerFactory, ChangeTrackerFactory>
+ {
+ it should_return_a_new_tracker = () => result.should_not_be_null();
+
+ because b = () =>
+ {
+ result = sut.create_for<Identifiable<Guid>>();
+ };
+
+ static IChangeTracker<Identifiable<Guid>> result;
+ }
+}
\ No newline at end of file
product/database/transactions/ChangeTrackerSpecs.cs
@@ -0,0 +1,103 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class ChangeTrackerSpecs
+ {
+
+ [Concern(typeof (ChangeTracker<Identifiable<Guid>>))]
+ public abstract class behaves_like_change_tracker :
+ concerns_for<IChangeTracker<Identifiable<Guid>>, ChangeTracker<Identifiable<Guid>>>
+ {
+ context c = () =>
+ {
+ mapper = the_dependency<ITrackerEntryMapper<Identifiable<Guid>>>();
+ registry = the_dependency<IStatementRegistry>();
+ };
+
+ static protected ITrackerEntryMapper<Identifiable<Guid>> mapper;
+ static protected IStatementRegistry registry;
+ }
+
+ [Concern(typeof (ChangeTracker<Identifiable<Guid>>))]
+ public class when_commit_that_changes_made_to_an_item : behaves_like_change_tracker
+ {
+ it should_save_the_changes_to_the_database = () => database.was_told_to(x => x.apply(statement));
+
+ context c = () =>
+ {
+ item = an<Identifiable<Guid>>();
+ statement = an<IStatement>();
+ database = an<IDatabase>();
+ var entry = an<ITrackerEntry<Identifiable<Guid>>>();
+
+ when_the(mapper).is_told_to(x => x.map_from(item)).it_will_return(entry);
+ when_the(entry).is_told_to(x => x.has_changes()).it_will_return(true);
+ when_the(entry).is_told_to(x => x.current).it_will_return(item);
+ when_the(registry).is_told_to(x => x.prepare_command_for(item)).it_will_return(statement);
+ };
+
+ because b = () =>
+ {
+ sut.register(item);
+ sut.commit_to(database);
+ };
+
+ static Identifiable<Guid> item;
+ static IDatabase database;
+ static IStatement statement;
+ }
+
+ [Concern(typeof (ChangeTracker<Identifiable<Guid>>))]
+ public class when_checking_if_there_are_changes_and_there_are : behaves_like_change_tracker
+ {
+ it should_tell_the_truth = () => result.should_be_true();
+
+ context c = () =>
+ {
+ item = an<Identifiable<Guid>>();
+ var registration = an<ITrackerEntry<Identifiable<Guid>>>();
+
+ when_the(mapper).is_told_to(x => x.map_from(item)).it_will_return(registration);
+ when_the(registration).is_told_to(x => x.has_changes()).it_will_return(true);
+ when_the(registration).is_told_to(x => x.current).it_will_return(item);
+ };
+
+ because b = () =>
+ {
+ sut.register(item);
+ result = sut.is_dirty();
+ };
+
+ static bool result;
+ static Identifiable<Guid> item;
+ }
+
+ [Concern(typeof (ChangeTracker<Identifiable<Guid>>))]
+ public class when_checking_if_there_are_changes_and_there_are_not : behaves_like_change_tracker
+ {
+ it should_tell_the_truth = () => result.should_be_false();
+
+ context c = () =>
+ {
+ item = an<Identifiable<Guid>>();
+ var entry = an<ITrackerEntry<Identifiable<Guid>>>();
+
+ when_the(mapper).is_told_to(x => x.map_from(item)).it_will_return(entry);
+ when_the(entry).is_told_to(x => x.has_changes()).it_will_return(false);
+ };
+
+ because b = () =>
+ {
+ sut.register(item);
+ result = sut.is_dirty();
+ };
+
+ static bool result;
+ static Identifiable<Guid> item;
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/Context.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+ public class Context : IContext
+ {
+ readonly IDictionary items;
+
+ public Context(IDictionary items)
+ {
+ this.items = items;
+ }
+
+ public bool contains<T>(IKey<T> key)
+ {
+ return key.is_found_in(items);
+ }
+
+ public void add<T>(IKey<T> key, T value)
+ {
+ key.add_value_to(items, value);
+ }
+
+ public T value_for<T>(IKey<T> key)
+ {
+ return key.parse_from(items);
+ }
+
+ public void remove<T>(IKey<T> key)
+ {
+ key.remove_from(items);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/ContextFactory.cs
@@ -0,0 +1,15 @@
+namespace momoney.database.transactions
+{
+ public interface IContextFactory
+ {
+ IContext create_for(IScopedStorage storage);
+ }
+
+ public class ContextFactory : IContextFactory
+ {
+ public IContext create_for(IScopedStorage storage)
+ {
+ return new Context(storage.provide_storage());
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/ContextFactorySpecs.cs
@@ -0,0 +1,31 @@
+using System.Collections;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.transactions
+{
+ public class ContextFactorySpecs
+ {
+ }
+
+ [Concern(typeof (ContextFactory))]
+ public class when_creating_a_new_context : concerns_for<IContextFactory, ContextFactory>
+ {
+ context c = () =>
+ {
+ scope = an<IScopedStorage>();
+ storage = an<IDictionary>();
+
+ when_the(scope).is_told_to(x => x.provide_storage()).it_will_return(storage);
+ };
+
+ because b = () => { result = sut.create_for(scope); };
+
+ it should_return_a_context_that_represents_the_specified_scope =
+ () => result.should_be_an_instance_of<Context>();
+
+ static IDictionary storage;
+ static IScopedStorage scope;
+ static IContext result;
+ }
+}
\ No newline at end of file
product/database/transactions/CurrentThread.cs
@@ -0,0 +1,19 @@
+using System.Threading;
+
+namespace momoney.database.transactions
+{
+ public class CurrentThread : IThread
+ {
+ public T provide_slot_for<T>() where T : class, new()
+ {
+ var slot = Thread.GetNamedDataSlot(create_key_for<T>());
+ if (null == Thread.GetData(slot)) Thread.SetData(slot, new T());
+ return (T) Thread.GetData(slot);
+ }
+
+ string create_key_for<T>()
+ {
+ return Thread.CurrentThread.ManagedThreadId + GetType().FullName + typeof (T).FullName;
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/IChangeTracker.cs
@@ -0,0 +1,17 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface IChangeTracker : IDisposable
+ {
+ bool is_dirty();
+ void commit_to(IDatabase database);
+ }
+
+ public interface IChangeTracker<T> : IChangeTracker where T : Identifiable<Guid>
+ {
+ void register(T value);
+ void delete(T entity);
+ }
+}
\ No newline at end of file
product/database/transactions/IChangeTrackerFactory.cs
@@ -0,0 +1,10 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface IChangeTrackerFactory
+ {
+ IChangeTracker<T> create_for<T>() where T : Identifiable<Guid>;
+ }
+}
\ No newline at end of file
product/database/transactions/IContext.cs
@@ -0,0 +1,10 @@
+namespace momoney.database.transactions
+{
+ public interface IContext
+ {
+ bool contains<T>(IKey<T> key);
+ void add<T>(IKey<T> key, T value);
+ T value_for<T>(IKey<T> key);
+ void remove<T>(IKey<T> key);
+ }
+}
\ No newline at end of file
product/database/transactions/IDatabase.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface IDatabase
+ {
+ IEnumerable<T> fetch_all<T>() where T : Identifiable<Guid>;
+ void apply(IStatement statement);
+ }
+}
\ No newline at end of file
product/database/transactions/IDatabaseConnection.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+
+namespace momoney.database.transactions
+{
+ public interface IDatabaseConnection : IDisposable
+ {
+ IEnumerable<T> query<T>();
+ IEnumerable<T> query<T>(Predicate<T> predicate);
+ void delete<T>(T entity);
+ void commit();
+ void store<T>(T entity);
+ }
+}
\ No newline at end of file
product/database/transactions/IdentityMapProxy.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class IdentityMapProxy<Key, Value> : IIdentityMap<Key, Value> where Value : Identifiable<Guid>
+ {
+ readonly IIdentityMap<Key, Value> real_map;
+ readonly IChangeTracker<Value> change_tracker;
+
+ public IdentityMapProxy(IChangeTracker<Value> change_tracker, IIdentityMap<Key, Value> real_map)
+ {
+ this.change_tracker = change_tracker;
+ this.real_map = real_map;
+ }
+
+ public IEnumerable<Value> all()
+ {
+ return real_map.all();
+ }
+
+ public void add(Key key, Value value)
+ {
+ change_tracker.register(value);
+ real_map.add(key, value);
+ }
+
+ public void update_the_item_for(Key key, Value new_value)
+ {
+ real_map.update_the_item_for(key, new_value);
+ }
+
+ public bool contains_an_item_for(Key key)
+ {
+ return real_map.contains_an_item_for(key);
+ }
+
+ public Value item_that_belongs_to(Key key)
+ {
+ return real_map.item_that_belongs_to(key);
+ }
+
+ public void evict(Key key)
+ {
+ change_tracker.delete(real_map.item_that_belongs_to(key));
+ real_map.evict(key);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/IdentityMapSpecs.cs
@@ -0,0 +1,95 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.transactions
+{
+ [Concern(typeof (IdentityMap<,>))]
+ public class behaves_like_identity_map : concerns_for<IIdentityMap<int, string>, IdentityMap<int, string>>
+ {
+ public override IIdentityMap<int, string> create_sut()
+ {
+ return new IdentityMap<int, string>();
+ }
+ }
+
+ [Concern(typeof (IdentityMap<,>))]
+ public class when_getting_an_item_from_the_identity_map_for_an_item_that_has_been_added : behaves_like_identity_map
+ {
+ it should_return_the_item_that_was_added_for_the_given_key = () => result.should_be_equal_to("1");
+
+ because b = () =>
+ {
+ sut.add(1, "1");
+ result = sut.item_that_belongs_to(1);
+ };
+
+ static string result;
+ }
+
+ [Concern(typeof (IdentityMap<,>))]
+ public class when_getting_an_item_from_the_identity_map_that_has_not_been_added : behaves_like_identity_map
+ {
+ it should_return_the_default_value_for_that_type = () => result.should_be_equal_to(null);
+
+ because b = () => { result = sut.item_that_belongs_to(2); };
+
+ static string result;
+ }
+
+ [Concern(typeof (IdentityMap<,>))]
+ public class when_checking_if_an_item_has_been_added_to_the_identity_map_that_has_been_added :
+ behaves_like_identity_map
+ {
+ it should_return_true = () => result.should_be_true();
+
+ because b = () =>
+ {
+ sut.add(10, "10");
+ result = sut.contains_an_item_for(10);
+ };
+
+ static bool result;
+ }
+
+ [Concern(typeof (IdentityMap<,>))]
+ public class when_checking_if_an_item_has_been_added_to_the_identity_map_that_has_not_been_added :
+ behaves_like_identity_map
+ {
+ it should_return_false = () => result.should_be_false();
+
+ because b = () => { result = sut.contains_an_item_for(9); };
+
+ static bool result;
+ }
+
+ [Concern(typeof (IdentityMap<,>))]
+ public class when_updating_the_value_for_a_key_that_has_already_been_added_to_the_identity_map :
+ behaves_like_identity_map
+ {
+ it should_replace_the_old_item_with_the_new_one = () => result.should_be_equal_to("7");
+
+ because b = () =>
+ {
+ sut.add(6, "6");
+ sut.update_the_item_for(6, "7");
+ result = sut.item_that_belongs_to(6);
+ };
+
+ static string result;
+ }
+
+ [Concern(typeof (IdentityMap<,>))]
+ public class when_updating_the_value_for_a_key_that_has_not_been_added_to_the_identity_map :
+ behaves_like_identity_map
+ {
+ it should_add_the_new_item = () => result.should_be_equal_to("3");
+
+ because b = () =>
+ {
+ sut.update_the_item_for(3, "3");
+ result = sut.item_that_belongs_to(3);
+ };
+
+ static string result;
+ }
+}
\ No newline at end of file
product/database/transactions/IIdentityMap.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+
+namespace momoney.database.transactions
+{
+ public interface IIdentityMap<TKey, TValue>
+ {
+ IEnumerable<TValue> all();
+ void add(TKey key, TValue value);
+ void update_the_item_for(TKey key, TValue new_value);
+ bool contains_an_item_for(TKey key);
+ TValue item_that_belongs_to(TKey key);
+ void evict(TKey key);
+ }
+
+ public class IdentityMap<TKey, TValue> : IIdentityMap<TKey, TValue>
+ {
+ readonly IDictionary<TKey, TValue> items_in_map;
+
+ public IdentityMap() : this(new Dictionary<TKey, TValue>())
+ {
+ }
+
+ public IdentityMap(IDictionary<TKey, TValue> items_in_map)
+ {
+ this.items_in_map = items_in_map;
+ }
+
+ public IEnumerable<TValue> all()
+ {
+ return items_in_map.Values;
+ }
+
+ public void add(TKey key, TValue value)
+ {
+ items_in_map.Add(key, value);
+ }
+
+ public void update_the_item_for(TKey key, TValue new_value)
+ {
+ if (contains_an_item_for(key)) items_in_map[key] = new_value;
+ else add(key, new_value);
+ }
+
+ public bool contains_an_item_for(TKey key)
+ {
+ return items_in_map.ContainsKey(key);
+ }
+
+ public TValue item_that_belongs_to(TKey key)
+ {
+ return contains_an_item_for(key) ? items_in_map[key] : default(TValue);
+ }
+
+ public void evict(TKey key)
+ {
+ if (contains_an_item_for(key)) items_in_map.Remove(key);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/IKey.cs
@@ -0,0 +1,12 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+ public interface IKey<T>
+ {
+ bool is_found_in(IDictionary items);
+ T parse_from(IDictionary items);
+ void remove_from(IDictionary items);
+ void add_value_to(IDictionary items, T value);
+ }
+}
\ No newline at end of file
product/database/transactions/IScopedStorage.cs
@@ -0,0 +1,9 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+ public interface IScopedStorage
+ {
+ IDictionary provide_storage();
+ }
+}
\ No newline at end of file
product/database/transactions/IStatement.cs
@@ -0,0 +1,7 @@
+namespace momoney.database.transactions
+{
+ public interface IStatement
+ {
+ void prepare(IDatabaseConnection connection);
+ }
+}
\ No newline at end of file
product/database/transactions/IStatementRegistry.cs
@@ -0,0 +1,11 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface IStatementRegistry
+ {
+ IStatement prepare_delete_statement_for<T>(T entity) where T : Identifiable<Guid>;
+ IStatement prepare_command_for<T>(T entity) where T : Identifiable<Guid>;
+ }
+}
\ No newline at end of file
product/database/transactions/IThread.cs
@@ -0,0 +1,7 @@
+namespace momoney.database.transactions
+{
+ public interface IThread
+ {
+ T provide_slot_for<T>() where T : class, new();
+ }
+}
\ No newline at end of file
product/database/transactions/PerThread.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace momoney.database.transactions
+{
+ public class PerThread : IContext
+ {
+ readonly IDictionary<int, LocalDataStoreSlot> slots;
+ readonly object mutex = new object();
+
+ public PerThread()
+ {
+ slots = new Dictionary<int, LocalDataStoreSlot>();
+ }
+
+ public bool contains<T>(IKey<T> key)
+ {
+ return key.is_found_in(get_items());
+ }
+
+ public void add<T>(IKey<T> key, T value)
+ {
+ key.add_value_to(get_items(), value);
+ }
+
+ public T value_for<T>(IKey<T> key)
+ {
+ return key.parse_from(get_items());
+ }
+
+ public void remove<T>(IKey<T> key)
+ {
+ key.remove_from(get_items());
+ }
+
+ IDictionary get_items()
+ {
+ var id = Thread.CurrentThread.ManagedThreadId;
+ within_lock(() =>
+ {
+ if (!slots.ContainsKey(id))
+ {
+ var slot = Thread.GetNamedDataSlot(GetType().FullName);
+ slots.Add(id, slot);
+ Thread.SetData(slot, new Hashtable());
+ }
+ });
+ return (IDictionary) Thread.GetData(slots[id]);
+ }
+
+ void within_lock(Action action)
+ {
+ lock (mutex) action();
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/PerThreadScopedStorage.cs
@@ -0,0 +1,19 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+ public class PerThreadScopedStorage : IScopedStorage
+ {
+ readonly IThread current_thread;
+
+ public PerThreadScopedStorage(IThread current_thread)
+ {
+ this.current_thread = current_thread;
+ }
+
+ public IDictionary provide_storage()
+ {
+ return current_thread.provide_slot_for<Hashtable>();
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/PerThreadScopedStorageSpecs.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.transactions
+{
+ public class PerThreadScopedStorageSpecs
+ {
+ [Concern(typeof (PerThreadScopedStorage))]
+ public class when_retrieving_the_storage_for_a_specific_thread :
+ concerns_for<IScopedStorage, PerThreadScopedStorage>
+ {
+ context c = () =>
+ {
+ thread = the_dependency<IThread>();
+ storage = new Hashtable();
+ when_the(thread)
+ .is_told_to(x => x.provide_slot_for<Hashtable>())
+ .it_will_return(storage);
+ };
+
+ because b = () =>
+ {
+ result = sut.provide_storage();
+ };
+
+ it should_return_the_storage_the_corresponds_to_the_current_thread = () => result.should_be_equal_to(storage);
+
+ static IDictionary result;
+ static IThread thread;
+ static Hashtable storage;
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/Session.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gorilla.Commons.Infrastructure.Logging;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface ISession : IDisposable
+ {
+ T find<T>(Guid guid) where T : Identifiable<Guid>;
+ IEnumerable<T> all<T>() where T : Identifiable<Guid>;
+ void save<T>(T entity) where T : Identifiable<Guid>;
+ void delete<T>(T entity) where T : Identifiable<Guid>;
+ void flush();
+ bool is_dirty();
+ }
+
+ public class Session : ISession
+ {
+ ITransaction transaction;
+ readonly IDatabase database;
+ readonly IDictionary<Type, object> identity_maps;
+ long id;
+
+ public Session(ITransaction transaction, IDatabase database)
+ {
+ this.database = database;
+ this.transaction = transaction;
+ identity_maps = new Dictionary<Type, object>();
+ id = DateTime.Now.Ticks;
+ }
+
+ public T find<T>(Guid id) where T : Identifiable<Guid>
+ {
+ if (get_identity_map_for<T>().contains_an_item_for(id))
+ {
+ return get_identity_map_for<T>().item_that_belongs_to(id);
+ }
+
+ var entity = database.fetch_all<T>().Single(x => x.id.Equals(id));
+ get_identity_map_for<T>().add(id, entity);
+ return entity;
+ }
+
+ public IEnumerable<T> all<T>() where T : Identifiable<Guid>
+ {
+ database
+ .fetch_all<T>()
+ .where(x => !get_identity_map_for<T>().contains_an_item_for(x.id))
+ .each(x => get_identity_map_for<T>().add(x.id, x));
+ return get_identity_map_for<T>().all();
+ }
+
+ public void save<T>(T entity) where T : Identifiable<Guid>
+ {
+ this.log().debug("saving {0}: {1}", id, entity);
+ get_identity_map_for<T>().add(entity.id, entity);
+ }
+
+ public void delete<T>(T entity) where T : Identifiable<Guid>
+ {
+ get_identity_map_for<T>().evict(entity.id);
+ }
+
+ public void flush()
+ {
+ this.log().debug("flushing session {0}", id);
+ transaction.commit_changes();
+ transaction = null;
+ }
+
+ public bool is_dirty()
+ {
+ this.log().debug("is dirty? {0}", id);
+ return null != transaction && transaction.is_dirty();
+ }
+
+ public void Dispose()
+ {
+ if (null != transaction) transaction.rollback_changes();
+ }
+
+ IIdentityMap<Guid, T> get_identity_map_for<T>() where T : Identifiable<Guid>
+ {
+ return identity_maps.ContainsKey(typeof (T))
+ ? identity_maps[typeof (T)].downcast_to<IIdentityMap<Guid, T>>()
+ : create_map_for<T>();
+ }
+
+ IIdentityMap<Guid, T> create_map_for<T>() where T : Identifiable<Guid>
+ {
+ var identity_map = transaction.create_for<T>();
+ identity_maps.Add(typeof (T), identity_map);
+ return identity_map;
+ }
+
+ public override string ToString()
+ {
+ return "session: {0}".formatted_using(id);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/SessionFactory.cs
@@ -0,0 +1,23 @@
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface ISessionFactory : Factory<ISession> {}
+
+ public class SessionFactory : ISessionFactory
+ {
+ readonly IDatabase database;
+ readonly IChangeTrackerFactory factory;
+
+ public SessionFactory(IDatabase database, IChangeTrackerFactory factory)
+ {
+ this.database = database;
+ this.factory = factory;
+ }
+
+ public ISession create()
+ {
+ return new Session(new Transaction(database, factory), database);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/SessionFactorySpecs.cs
@@ -0,0 +1,21 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.transactions
+{
+ public class SessionFactorySpecs
+ {
+ [Concern(typeof (SessionFactory))]
+ public class when_creating_a_new_session : concerns_for<ISessionFactory, SessionFactory>
+ {
+ it should_return_a_new_session = () => result.should_not_be_null();
+
+ because b = () =>
+ {
+ result = sut.create();
+ };
+
+ static ISession result;
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/SessionNotStartedException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace momoney.database.transactions
+{
+ public class SessionNotStartedException : Exception
+ {
+ public SessionNotStartedException() : base("A session could not be found. Did you forget to open a session?")
+ {
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/SessionProvider.cs
@@ -0,0 +1,25 @@
+namespace momoney.database.transactions
+{
+ public interface ISessionProvider
+ {
+ ISession get_the_current_session();
+ }
+
+ public class SessionProvider : ISessionProvider
+ {
+ readonly IContext context;
+ readonly IKey<ISession> session_key;
+
+ public SessionProvider(IContext context, IKey<ISession> session_key)
+ {
+ this.context = context;
+ this.session_key = session_key;
+ }
+
+ public ISession get_the_current_session()
+ {
+ if (!context.contains(session_key)) throw new SessionNotStartedException();
+ return context.value_for(session_key);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/SessionSpecs.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Collections.Generic;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class SessionSpecs
+ {
+ public class behaves_like_session : concerns_for<ISession, Session>
+ {
+ context c = () =>
+ {
+ transaction = the_dependency<ITransaction>();
+ database = the_dependency<IDatabase>();
+ };
+
+ static protected ITransaction transaction;
+ static protected IDatabase database;
+ }
+
+ [Concern(typeof (Session))]
+ public class when_saving_a_transient_item_to_a_session : behaves_like_session
+ {
+ it should_add_the_entity_to_the_identity_map = () => map.was_told_to(x => x.add(guid, entity));
+
+ context c = () =>
+ {
+ guid = Guid.NewGuid();
+ entity = an<ITestEntity>();
+ map = an<IIdentityMap<Guid, ITestEntity>>();
+
+ when_the(entity).is_told_to(x => x.id).it_will_return(guid);
+ when_the(transaction).is_told_to(x => x.create_for<ITestEntity>()).it_will_return(map);
+ };
+
+ because b = () => sut.save(entity);
+
+ static ITestEntity entity;
+ static IIdentityMap<Guid, ITestEntity> map;
+ static Id<Guid> guid;
+ }
+
+ [Concern(typeof (Session))]
+ public class when_commiting_the_changes_made_in_a_session : behaves_like_session
+ {
+ it should_commit_all_the_changes_from_the_running_transaction =
+ () => transaction.was_told_to(x => x.commit_changes());
+
+ it should_not_rollback_any_changes_from_the_running_transaction =
+ () => transaction.was_not_told_to(x => x.rollback_changes());
+
+ because b = () =>
+ {
+ sut.flush();
+ sut.Dispose();
+ };
+ }
+
+ [Concern(typeof (Session))]
+ public class when_closing_a_session_before_flushing_the_changes : behaves_like_session
+ {
+ it should_rollback_any_changes_made_in_the_current_transaction =
+ () => transaction.was_told_to(x => x.rollback_changes());
+
+ because b = () => sut.Dispose();
+ }
+
+ [Concern(typeof (Session))]
+ public class when_loading_all_instances_of_a_certain_type_and_some_have_already_been_loaded : behaves_like_session
+ {
+ it should_return_the_items_from_the_cache = () => results.should_contain(cached_item);
+
+ it should_exclude_duplicates_from_the_database = () => results.should_not_contain(database_item);
+
+ it should_add_items_from_the_database_to_the_identity_map =
+ () => identity_map.was_told_to(x => x.add(id_of_the_uncached_item, uncached_item));
+
+ context c = () =>
+ {
+ id = Guid.NewGuid();
+ id_of_the_uncached_item = Guid.NewGuid();
+ identity_map = an<IIdentityMap<Guid, ITestEntity>>();
+ cached_item = an<ITestEntity>();
+ database_item = an<ITestEntity>();
+ uncached_item = an<ITestEntity>();
+
+ when_the(cached_item).is_told_to(x => x.id).it_will_return(id);
+ when_the(database_item).is_told_to(x => x.id).it_will_return(id);
+ when_the(uncached_item).is_told_to(x => x.id).it_will_return(id_of_the_uncached_item);
+ when_the(transaction).is_told_to(x => x.create_for<ITestEntity>()).it_will_return(identity_map);
+ when_the(identity_map).is_told_to(x => x.contains_an_item_for(id)).it_will_return(true);
+ when_the(identity_map).is_told_to(x => x.all()).it_will_return(cached_item);
+ when_the(database).is_told_to(x => x.fetch_all<ITestEntity>())
+ .it_will_return(database_item, uncached_item);
+ };
+
+ because b = () =>
+ {
+ sut.find<ITestEntity>(id);
+ results = sut.all<ITestEntity>();
+ };
+
+ static IEnumerable<ITestEntity> results;
+ static Id<Guid> id;
+ static Id<Guid> id_of_the_uncached_item;
+ static ITestEntity cached_item;
+ static ITestEntity database_item;
+ static IIdentityMap<Guid, ITestEntity> identity_map;
+ static ITestEntity uncached_item;
+ }
+
+ [Concern(typeof (Session))]
+ public class when_looking_up_a_specific_entity_by_its_id_and_it_has_not_been_loaded_into_the_cache :
+ behaves_like_session
+ {
+ it should_return_that_item = () =>
+ {
+ result.should_be_equal_to(correct_item);
+ };
+
+ it should_add_that_item_to_the_identity_map = () => map.was_told_to(x => x.add(id, correct_item));
+
+ context c = () =>
+ {
+ id = Guid.NewGuid();
+ wrong_item = an<ITestEntity>();
+ correct_item = an<ITestEntity>();
+ map = an<IIdentityMap<Guid, ITestEntity>>();
+ when_the(wrong_item).is_told_to(x => x.id).it_will_return<Id<Guid>>(Guid.NewGuid());
+ when_the(correct_item).is_told_to(x => x.id).it_will_return(id);
+ when_the(database)
+ .is_told_to(x => x.fetch_all<ITestEntity>())
+ .it_will_return(wrong_item, correct_item);
+ when_the(transaction).is_told_to(x => x.create_for<ITestEntity>())
+ .it_will_return(map);
+ };
+
+ because b = () =>
+ {
+ result = sut.find<ITestEntity>(id);
+ };
+
+ static Id<Guid> id;
+ static Identifiable<Guid> result;
+ static ITestEntity correct_item;
+ static ITestEntity wrong_item;
+ static IIdentityMap<Guid, ITestEntity> map;
+ }
+
+ [Concern(typeof (Session))]
+ public class when_deleting_an_item_from_the_database : behaves_like_session
+ {
+ it should_remove_that_item_from_the_cache = () => map.was_told_to(x => x.evict(id));
+
+ context c = () =>
+ {
+ id = Guid.NewGuid();
+ entity = an<ITestEntity>();
+ map = an<IIdentityMap<Guid, ITestEntity>>();
+
+ when_the(entity).is_told_to(x => x.id).it_will_return(id);
+ when_the(transaction).is_told_to(x => x.create_for<ITestEntity>()).it_will_return(map);
+ when_the(database).is_told_to(x => x.fetch_all<ITestEntity>()).it_will_return(entity);
+ };
+
+ because b = () =>
+ {
+ sut.find<ITestEntity>(id);
+ sut.delete(entity);
+ };
+
+ static Id<Guid> id;
+ static IIdentityMap<Guid, ITestEntity> map;
+ static ITestEntity entity;
+ }
+
+ public interface ITestEntity : Identifiable<Guid> {}
+ }
+}
\ No newline at end of file
product/database/transactions/SingletonScopedStorage.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+ public class SingletonScopedStorage : IScopedStorage
+ {
+ static readonly IDictionary storage = new Hashtable();
+
+ public IDictionary provide_storage()
+ {
+ return storage;
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/StatementRegistry.cs
@@ -0,0 +1,48 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class StatementRegistry : IStatementRegistry
+ {
+ public IStatement prepare_delete_statement_for<T>(T entity) where T : Identifiable<Guid>
+ {
+ return new DeletionStatement<T>(entity);
+ }
+
+ public IStatement prepare_command_for<T>(T entity) where T : Identifiable<Guid>
+ {
+ return new SaveOrUpdateStatement<T>(entity);
+ }
+ }
+
+ public class SaveOrUpdateStatement<T> : IStatement where T : Identifiable<Guid>
+ {
+ readonly T entity;
+
+ public SaveOrUpdateStatement(T entity)
+ {
+ this.entity = entity;
+ }
+
+ public void prepare(IDatabaseConnection connection)
+ {
+ connection.store(entity);
+ }
+ }
+
+ public class DeletionStatement<T> : IStatement
+ {
+ readonly T entity;
+
+ public DeletionStatement(T entity)
+ {
+ this.entity = entity;
+ }
+
+ public void prepare(IDatabaseConnection connection)
+ {
+ connection.delete(entity);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/TrackerEntry.cs
@@ -0,0 +1,47 @@
+using System.Reflection;
+using Gorilla.Commons.Infrastructure.Logging;
+
+namespace momoney.database.transactions
+{
+ public interface ITrackerEntry<T>
+ {
+ T current { get; }
+ bool has_changes();
+ }
+
+ public class TrackerEntry<T> : ITrackerEntry<T>
+ {
+ readonly T original;
+
+ public TrackerEntry(T original, T current)
+ {
+ this.original = original;
+ this.current = current;
+ }
+
+ public T current { get; set; }
+
+ public bool has_changes()
+ {
+ this.log().debug("checking for changes");
+ var type = original.GetType();
+ foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
+ {
+ var original_value = field.GetValue(original);
+ var current_value = field.GetValue(current);
+ if (original_value == null && current_value != null)
+ {
+ this.log().debug("{0} has changes: {1}", field, original);
+ return true;
+ }
+ if (original_value != null && !original_value.Equals(current_value))
+ {
+ this.log().debug("{0} has changes: {1}", field, original);
+ return true;
+ }
+ }
+ this.log().debug("has no changes: {0}", original);
+ return false;
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/TrackerEntryMapper.cs
@@ -0,0 +1,27 @@
+using Gorilla.Commons.Infrastructure.Cloning;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface ITrackerEntryMapper<T> : Mapper<T, ITrackerEntry<T>> {}
+
+ public class TrackerEntryMapper<T> : ITrackerEntryMapper<T>
+ {
+ readonly IPrototype prototype;
+
+ public TrackerEntryMapper(IPrototype prototype)
+ {
+ this.prototype = prototype;
+ }
+
+ public ITrackerEntry<T> map_from(T item)
+ {
+ return new TrackerEntry<T>(create_prototype(item), item);
+ }
+
+ T create_prototype(T item)
+ {
+ return prototype.clone(item);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/TrackerEntrySpecs.cs
@@ -0,0 +1,145 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class TrackerEntrySpecs {}
+
+ public abstract class behaves_like_tracker_entry : concerns_for<ITrackerEntry<Pillow>> {}
+
+ [Concern(typeof (ITrackerEntry<>))]
+ public class when_comparing_the_current_instance_of_a_component_with_its_original_and_it_has_changes :
+ behaves_like_tracker_entry
+ {
+ it should_indicate_that_there_are_changes = () => result.should_be_true();
+
+ because b = () =>
+ {
+ result = sut.has_changes();
+ };
+
+ public override ITrackerEntry<Pillow> create_sut()
+ {
+ return new TrackerEntry<Pillow>(new Pillow("pink"), new Pillow("yellow"));
+ }
+
+ static bool result;
+ }
+
+ [Concern(typeof (ITrackerEntry<>))]
+ public class when_the_original_instance_has_a_null_field_that_is_now_not_null :
+ behaves_like_tracker_entry
+ {
+ it should_indicate_that_there_are_changes = () => result.should_be_true();
+
+ because b = () =>
+ {
+ result = sut.has_changes();
+ };
+
+ public override ITrackerEntry<Pillow> create_sut()
+ {
+ return new TrackerEntry<Pillow>(new Pillow(null), new Pillow("yellow"));
+ }
+
+ static bool result;
+ }
+
+ [Concern(typeof (ITrackerEntry<>))]
+ public class when_the_original_instance_had_a_non_null_field_and_the_current_instance_has_a_null_field :
+ behaves_like_tracker_entry
+ {
+ it should_indicate_that_there_are_changes = () => result.should_be_true();
+
+ because b = () =>
+ {
+ result = sut.has_changes();
+ };
+
+ context c = () =>
+ {
+ var id = Guid.NewGuid();
+ original = new Pillow("green", id);
+ current = new Pillow(null, id);
+ };
+
+ public override ITrackerEntry<Pillow> create_sut()
+ {
+ return new TrackerEntry<Pillow>(original, current);
+ }
+
+ static bool result;
+ static Pillow original;
+ static Pillow current;
+ }
+
+ [Concern(typeof (ITrackerEntry<>))]
+ public class when_the_original_instance_has_the_same_value_as_the_current_instance :
+ behaves_like_tracker_entry
+ {
+ it should_indicate_that_there_are_no_changes = () => result.should_be_false();
+
+ because b = () =>
+ {
+ result = sut.has_changes();
+ };
+
+ context c = () =>
+ {
+ var id = Guid.NewGuid();
+ original = new Pillow("green", id);
+ current = new Pillow("green", id);
+ };
+
+ public override ITrackerEntry<Pillow> create_sut()
+ {
+ return new TrackerEntry<Pillow>(original, current);
+ }
+
+ static bool result;
+ static Pillow original;
+ static Pillow current;
+ }
+
+ public class Pillow : Identifiable<Guid>
+ {
+ readonly string color;
+
+ public Pillow(string color) : this(color, Guid.NewGuid()) {}
+
+ public Pillow(string color, Guid id)
+ {
+ this.color = color;
+ this.id = id;
+ }
+
+ public Id<Guid> id { get; set; }
+
+ public bool Equals(Pillow other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return other.id.Equals(id);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (Pillow)) return false;
+ return Equals((Pillow) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return id.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return "{0} id: {1}".formatted_using(base.ToString(), id);
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/Transaction.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public interface ITransaction
+ {
+ IIdentityMap<Guid, T> create_for<T>() where T : Identifiable<Guid>;
+ void commit_changes();
+ void rollback_changes();
+ bool is_dirty();
+ }
+
+ public class Transaction : ITransaction
+ {
+ readonly IDatabase database;
+ readonly IChangeTrackerFactory factory;
+ readonly IDictionary<Type, IChangeTracker> change_trackers;
+
+ public Transaction(IDatabase database, IChangeTrackerFactory factory)
+ {
+ this.factory = factory;
+ this.database = database;
+ change_trackers = new Dictionary<Type, IChangeTracker>();
+ }
+
+ public IIdentityMap<Guid, T> create_for<T>() where T : Identifiable<Guid>
+ {
+ return new IdentityMapProxy<Guid, T>(get_change_tracker_for<T>(), new IdentityMap<Guid, T>());
+ }
+
+ public void commit_changes()
+ {
+ change_trackers.Values.where(x => x.is_dirty()).each(x => x.commit_to(database));
+ }
+
+ public void rollback_changes()
+ {
+ change_trackers.each(x => x.Value.Dispose());
+ change_trackers.Clear();
+ }
+
+ public bool is_dirty()
+ {
+ return change_trackers.Values.Count(x => x.is_dirty()) > 0;
+ }
+
+ IChangeTracker<T> get_change_tracker_for<T>() where T : Identifiable<Guid>
+ {
+ if (!change_trackers.ContainsKey(typeof (T))) change_trackers.Add(typeof (T), factory.create_for<T>());
+ return change_trackers[typeof (T)].downcast_to<IChangeTracker<T>>();
+ }
+ }
+}
\ No newline at end of file
product/database/transactions/TransactionSpecs.cs
@@ -0,0 +1,113 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+ public class TransactionSpecs
+ {
+ }
+
+ [Concern(typeof (Transaction))]
+ public class behaves_like_transaction : concerns_for<ITransaction, Transaction>
+ {
+ context c = () =>
+ {
+ database = the_dependency<IDatabase>();
+ factory = the_dependency<IChangeTrackerFactory>();
+ };
+
+ static protected IDatabase database;
+ static protected IChangeTrackerFactory factory;
+ }
+
+ [Concern(typeof (Transaction))]
+ public class when_creating_an_identity_map_for_a_specific_entity : behaves_like_transaction
+ {
+ it should_return_a_new_identity_map = () => result.should_not_be_null();
+
+ because b = () => { result = sut.create_for<Identifiable<Guid>>(); };
+
+ static IIdentityMap<Guid, Identifiable<Guid>> result;
+ }
+
+ [Concern(typeof (Transaction))]
+ public class when_committing_a_transaction_and_an_item_in_the_identity_map_has_changed : behaves_like_transaction
+ {
+ it should_commit_the_changes_to_that_item =
+ () => tracker.was_told_to<IChangeTracker<IMovie>>(x => x.commit_to(database));
+
+ context c = () =>
+ {
+ movie = new Movie("Goldeneye");
+ tracker = an<IChangeTracker<IMovie>>();
+
+ when_the(factory).is_told_to(x => x.create_for<IMovie>()).it_will_return(tracker);
+ when_the(tracker).is_told_to(x => x.is_dirty()).it_will_return(true);
+ };
+
+
+ because b = () =>
+ {
+ sut.create_for<IMovie>().add(movie.id, movie);
+ movie.change_name_to("Austin Powers");
+ sut.commit_changes();
+ };
+
+ static IMovie movie;
+ static IChangeTracker<IMovie> tracker;
+ }
+
+ [Concern(typeof (Transaction))]
+ public class when_deleting_a_set_of_entities_from_the_database : behaves_like_transaction
+ {
+ it should_prepare_to_delete_that_item_form_the_database = () => tracker.was_told_to(x => x.delete(movie));
+
+ it should_delete_all_items_marked_for_deletion = () => tracker.was_told_to(x => x.commit_to(database));
+
+ context c = () =>
+ {
+ movie = new Movie("Goldeneye");
+ tracker = an<IChangeTracker<IMovie>>();
+
+ when_the(factory).is_told_to(x => x.create_for<IMovie>()).it_will_return(tracker);
+ when_the(tracker).is_told_to(x => x.is_dirty()).it_will_return(true);
+ };
+
+ because b = () =>
+ {
+ var map = sut.create_for<IMovie>();
+ map.add(movie.id, movie);
+ map.evict(movie.id);
+ sut.commit_changes();
+ };
+
+ static IMovie movie;
+ static IChangeTracker<IMovie> tracker;
+ }
+
+ public interface IMovie : Identifiable<Guid>
+ {
+ string name { get; }
+ void change_name_to(string name);
+ }
+
+ internal class Movie : IMovie
+ {
+ public Movie(string name)
+ {
+ id = Guid.NewGuid();
+ this.name = name;
+ }
+
+ public string name { get; set; }
+
+ public void change_name_to(string new_name)
+ {
+ name = new_name;
+ }
+
+ public Id<Guid> id { get; set; }
+ }
+}
\ No newline at end of file
product/database/transactions/TypedKey.cs
@@ -0,0 +1,50 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+ public class TypedKey<T> : IKey<T>
+ {
+ public bool is_found_in(IDictionary items)
+ {
+ return items.Contains(create_key());
+ }
+
+ public T parse_from(IDictionary items)
+ {
+ return (T) items[create_key()];
+ }
+
+ public void remove_from(IDictionary items)
+ {
+ if (is_found_in(items)) items.Remove(create_key());
+ }
+
+ public void add_value_to(IDictionary items, T value)
+ {
+ items[create_key()] = value;
+ }
+
+ public bool Equals(TypedKey<T> obj)
+ {
+ return !ReferenceEquals(null, obj);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != typeof (TypedKey<T>)) return false;
+ return Equals((TypedKey<T>) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return GetType().GetHashCode();
+ }
+
+ string create_key()
+ {
+ return GetType().FullName;
+ }
+ }
+}
\ No newline at end of file
product/database/database.csproj
@@ -0,0 +1,152 @@
+๏ปฟ<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{580E68A8-EDEE-4350-8BBE-A053645B0F83}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>momoney.database</RootNamespace>
+ <AssemblyName>momoney.database</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="bdddoc, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\test\bdd.doc\bdddoc.dll</HintPath>
+ </Reference>
+ <Reference Include="Db4objects.Db4o, Version=7.5.57.11498, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\app\db40\Db4objects.Db4o.dll</HintPath>
+ </Reference>
+ <Reference Include="Db4objects.Db4o.Linq, Version=7.5.57.11498, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\app\db40\Db4objects.Db4o.Linq.dll</HintPath>
+ </Reference>
+ <Reference Include="developwithpassion.bdd, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\test\developwithpassion\developwithpassion.bdd.dll</HintPath>
+ </Reference>
+ <Reference Include="gorilla.commons.infrastructure, Version=2009.5.5.1633, Culture=neutral, PublicKeyToken=687787ccb6c36c9f, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\app\gorilla\gorilla.commons.infrastructure.dll</HintPath>
+ </Reference>
+ <Reference Include="gorilla.commons.utility, Version=2009.5.5.1633, Culture=neutral, PublicKeyToken=687787ccb6c36c9f, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\app\gorilla\gorilla.commons.utility.dll</HintPath>
+ </Reference>
+ <Reference Include="gorilla.testing, Version=2009.5.5.194, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\test\gorilla\gorilla.testing.dll</HintPath>
+ </Reference>
+ <Reference Include="MbUnit.Framework, Version=2.4.2.175, Culture=neutral, PublicKeyToken=5e72ecd30bc408d5">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\test\mbunit\MbUnit.Framework.dll</HintPath>
+ </Reference>
+ <Reference Include="Rhino.Mocks, Version=3.5.0.1337, Culture=neutral, PublicKeyToken=0b3305902db7183f, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\build\lib\test\rhino.mocks\Rhino.Mocks.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Xml.Linq">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data.DataSetExtensions">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="db4o\ConfigureObjectContainerStep.cs" />
+ <Compile Include="db4o\Spiking\db40_spike_specs.cs" />
+ <Compile Include="repositories\AccountHolderRepository.cs" />
+ <Compile Include="repositories\BillRepository.cs" />
+ <Compile Include="repositories\CompanyRepository.cs" />
+ <Compile Include="repositories\IncomeRepository.cs" />
+ <Compile Include="db4o\ConfigureDatabaseStep.cs" />
+ <Compile Include="db4o\ConnectionFactory.cs" />
+ <Compile Include="ObjectDatabase.cs" />
+ <Compile Include="db4o\DatabaseConnection.cs" />
+ <Compile Include="IConnectionFactory.cs" />
+ <Compile Include="IDatabaseConfiguration.cs" />
+ <Compile Include="transactions\ChangeTracker.cs" />
+ <Compile Include="transactions\ChangeTrackerFactory.cs" />
+ <Compile Include="transactions\ChangeTrackerFactorySpecs.cs" />
+ <Compile Include="transactions\ChangeTrackerSpecs.cs" />
+ <Compile Include="transactions\Context.cs" />
+ <Compile Include="transactions\ContextFactory.cs" />
+ <Compile Include="transactions\ContextFactorySpecs.cs" />
+ <Compile Include="transactions\CurrentThread.cs" />
+ <Compile Include="transactions\IChangeTracker.cs" />
+ <Compile Include="transactions\IChangeTrackerFactory.cs" />
+ <Compile Include="transactions\IContext.cs" />
+ <Compile Include="transactions\IDatabase.cs" />
+ <Compile Include="transactions\IDatabaseConnection.cs" />
+ <Compile Include="transactions\IdentityMapProxy.cs" />
+ <Compile Include="transactions\IdentityMapSpecs.cs" />
+ <Compile Include="transactions\IIdentityMap.cs" />
+ <Compile Include="transactions\IKey.cs" />
+ <Compile Include="transactions\IScopedStorage.cs" />
+ <Compile Include="transactions\IStatement.cs" />
+ <Compile Include="transactions\IStatementRegistry.cs" />
+ <Compile Include="transactions\IThread.cs" />
+ <Compile Include="transactions\PerThread.cs" />
+ <Compile Include="transactions\PerThreadScopedStorage.cs" />
+ <Compile Include="transactions\PerThreadScopedStorageSpecs.cs" />
+ <Compile Include="transactions\Session.cs" />
+ <Compile Include="transactions\SessionFactory.cs" />
+ <Compile Include="transactions\SessionFactorySpecs.cs" />
+ <Compile Include="transactions\SessionNotStartedException.cs" />
+ <Compile Include="transactions\SessionProvider.cs" />
+ <Compile Include="transactions\SessionSpecs.cs" />
+ <Compile Include="transactions\SingletonScopedStorage.cs" />
+ <Compile Include="transactions\StatementRegistry.cs" />
+ <Compile Include="transactions\TrackerEntry.cs" />
+ <Compile Include="transactions\TrackerEntryMapper.cs" />
+ <Compile Include="transactions\TrackerEntrySpecs.cs" />
+ <Compile Include="transactions\Transaction.cs" />
+ <Compile Include="transactions\TransactionSpecs.cs" />
+ <Compile Include="transactions\TypedKey.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Domain\domain.csproj">
+ <Project>{BE790BCC-4412-473F-9D0A-5AA48FE7A74F}</Project>
+ <Name>domain</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="Properties\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
product/database/IConnectionFactory.cs
@@ -0,0 +1,10 @@
+using Gorilla.Commons.Infrastructure.FileSystem;
+using momoney.database.transactions;
+
+namespace momoney.database
+{
+ public interface IConnectionFactory
+ {
+ IDatabaseConnection open_connection_to(File the_path_to_the_database_file);
+ }
+}
\ No newline at end of file
product/database/IDatabaseConfiguration.cs
@@ -0,0 +1,11 @@
+using Gorilla.Commons.Infrastructure.FileSystem;
+
+namespace momoney.database
+{
+ public interface IDatabaseConfiguration
+ {
+ void open(File file);
+ void copy_to(string path);
+ void close(string name);
+ }
+}
\ No newline at end of file
product/database/ObjectDatabase.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Gorilla.Commons.Infrastructure.FileSystem;
+using gorilla.commons.utility;
+using momoney.database.transactions;
+using File = Gorilla.Commons.Infrastructure.FileSystem.File;
+
+namespace momoney.database
+{
+ public class ObjectDatabase : IDatabase, IDatabaseConfiguration
+ {
+ readonly IConnectionFactory factory;
+ File path;
+
+ public ObjectDatabase(IConnectionFactory factory)
+ {
+ this.factory = factory;
+ path = new ApplicationFile(Path.GetTempFileName());
+ }
+
+ public IEnumerable<T> fetch_all<T>() where T : Identifiable<Guid>
+ {
+ using (var connection = factory.open_connection_to(path_to_database()))
+ {
+ return connection.query<T>().ToList();
+ }
+ }
+
+ public void apply(IStatement statement)
+ {
+ using (var connection = factory.open_connection_to(path_to_database()))
+ {
+ statement.prepare(connection);
+ connection.commit();
+ }
+ }
+
+ public void open(File file)
+ {
+ path = new ApplicationFile(Path.GetTempFileName());
+ file.copy_to(path.path);
+ }
+
+ public void copy_to(string new_path)
+ {
+ path.copy_to(new_path);
+ }
+
+ public void close(string name)
+ {
+ path.delete();
+ path = new ApplicationFile(Path.GetTempFileName());
+ }
+
+ File path_to_database()
+ {
+ return path;
+ }
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/ClosingProjectEvent.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class ClosingProjectEvent : IEvent
+ {
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/ClosingTheApplication.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class ClosingTheApplication : IEvent
+ {
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/FinishedRunningCommand.cs
@@ -0,0 +1,14 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class FinishedRunningCommand : IEvent
+ {
+ public FinishedRunningCommand(object running_command)
+ {
+ completed_action = running_command;
+ }
+
+ public object completed_action { get; private set; }
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/NewProjectOpened.cs
@@ -0,0 +1,14 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class NewProjectOpened : IEvent
+ {
+ public NewProjectOpened(string path)
+ {
+ this.path = path;
+ }
+
+ public string path { private set; get; }
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/SavedChangesEvent.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class SavedChangesEvent : IEvent
+ {
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/StartedRunningCommand.cs
@@ -0,0 +1,14 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class StartedRunningCommand : IEvent
+ {
+ public StartedRunningCommand(object running_command)
+ {
+ running_action = running_command;
+ }
+
+ public object running_action { get; private set; }
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/UnhandledErrorOccurred.cs
@@ -0,0 +1,15 @@
+using System;
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class UnhandledErrorOccurred : IEvent
+ {
+ public UnhandledErrorOccurred(Exception error)
+ {
+ this.error = error;
+ }
+
+ public Exception error { get; private set; }
+ }
+}
\ No newline at end of file
product/Presentation/Model/eventing/UnsavedChangesEvent.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+ public class UnsavedChangesEvent : IEvent
+ {
+ }
+}
\ No newline at end of file
product/Presentation/Model/Menu/Help/DisplayInformationAboutTheApplication.cs
@@ -0,0 +1,23 @@
+using gorilla.commons.utility;
+using momoney.presentation.presenters;
+using MoMoney.Presentation.Presenters;
+
+namespace MoMoney.Presentation.model.menu.help
+{
+ public interface IDisplayInformationAboutTheApplication : Command {}
+
+ public class DisplayInformationAboutTheApplication : IDisplayInformationAboutTheApplication
+ {
+ public DisplayInformationAboutTheApplication(IRunPresenterCommand run_presenter)
+ {
+ this.run_presenter = run_presenter;
+ }
+
+ public void run()
+ {
+ run_presenter.run<IAboutApplicationPresenter>();
+ }
+
+ readonly IRunPresenterCommand run_presenter;
+ }
+}
\ No newline at end of file
product/Presentation/Presenters/DisplayTheSplashScreen.cs
@@ -0,0 +1,34 @@
+using System;
+using MoMoney.Presentation.Presenters;
+using momoney.presentation.views;
+using MoMoney.Service.Infrastructure.Threading;
+
+namespace momoney.presentation.presenters
+{
+ public class DisplayTheSplashScreen : ISplashScreenState
+ {
+ readonly ITimer timer;
+ readonly ISplashScreenView view;
+ readonly ISplashScreenPresenter presenter;
+
+ public DisplayTheSplashScreen(ITimer timer, ISplashScreenView view, ISplashScreenPresenter presenter)
+ {
+ this.timer = timer;
+ this.view = view;
+ this.presenter = presenter;
+ timer.start_notifying(presenter, new TimeSpan(50));
+ }
+
+ public void update()
+ {
+ if (view.current_opacity() < 1)
+ {
+ view.increment_the_opacity();
+ }
+ else
+ {
+ timer.stop_notifying(presenter);
+ }
+ }
+ }
+}
\ No newline at end of file