diff --git a/StudentManager.sln b/StudentManager.sln index e4e24fb..71df75a 100644 --- a/StudentManager.sln +++ b/StudentManager.sln @@ -18,6 +18,7 @@ Global StartupItem = StudentManager\StudentManager.csproj Policies = $0 $0.TextStylePolicy = $1 + $1.RemoveTrailingWhitespace = True $1.inheritsSet = VisualStudio $1.inheritsScope = text/plain $1.scope = text/x-csharp @@ -31,12 +32,6 @@ Global $2.EventAddBraceStyle = NextLine $2.EventRemoveBraceStyle = NextLine $2.StatementBraceStyle = NextLine - $2.ElseNewLinePlacement = NewLine - $2.CatchNewLinePlacement = NewLine - $2.FinallyNewLinePlacement = NewLine - $2.WhileNewLinePlacement = DoNotCare - $2.ArrayInitializerWrapping = DoNotChange - $2.ArrayInitializerBraceStyle = NextLine $2.BeforeMethodDeclarationParentheses = False $2.BeforeMethodCallParentheses = False $2.BeforeConstructorDeclarationParentheses = False @@ -48,8 +43,9 @@ Global $2.scope = text/x-csharp $0.TextStylePolicy = $3 $3.FileWidth = 120 - $3.TabsToSpaces = False - $3.inheritsSet = VisualStudio + $3.TabWidth = 4 + $3.EolMarker = Unix + $3.inheritsSet = Mono $3.inheritsScope = text/plain $3.scope = text/plain $0.StandardHeader = $4 diff --git a/StudentManager.userprefs b/StudentManager.userprefs index 00b9ae3..18bdbc2 100644 --- a/StudentManager.userprefs +++ b/StudentManager.userprefs @@ -1,19 +1,32 @@  - + - - - - + + + + + + + + + + + + + + + + + + - - - + + @@ -24,15 +37,7 @@ - - - - - - - - - + diff --git a/StudentManager/Class.cs b/StudentManager/Class.cs index 55efc8b..f688913 100755 --- a/StudentManager/Class.cs +++ b/StudentManager/Class.cs @@ -3,8 +3,9 @@ namespace StudentManager { - public struct Room + public class Room : Identity { + [Identity.ID] public String Name { get; set; } } @@ -15,8 +16,9 @@ public struct TimeSlot public DateTime EndTime { get; set; } } - public class Class: Identity + public class Class : Identity { + [Identity.ID] public string Name { get; set; } public string Teacher { get; set; } diff --git a/StudentManager/Identity.cs b/StudentManager/Identity.cs index 92671c8..7200859 100644 --- a/StudentManager/Identity.cs +++ b/StudentManager/Identity.cs @@ -20,30 +20,75 @@ // along with this program. If not, see . // using System; +using System.Linq; +using System.Reflection; namespace StudentManager { - public abstract class Identity: IComparable + /// + /// Base class for identities. + /// + /// + /// The single purpose for this class' existence is to provide + /// simple and uniform ID and equality check. Inheriting classes + /// are expected to have a single public property decorated with + /// the [ID] attribute, which will be used in equality comparisions. + /// If there are multiple properties with ID attribute, only the + /// first one will be used. + /// + public abstract class Identity : IEquatable { - public int ID { get; set; } - - - public int CompareTo(Identity other) + [AttributeUsage(System.AttributeTargets.Property)] + public class IDAttribute : System.Attribute { - return this.ID - other.ID; } - - public override bool Equals(Object other) + + MethodInfo IdGetMethod; + + public Identity() + { + var idProperty = (from prop in this.GetType().GetProperties() + from attr in prop.GetCustomAttributes(false) + where attr is IDAttribute + select prop).SingleOrDefault(); + + // Not null, readable and not an indexer + if (idProperty != null && + idProperty.CanRead && + idProperty.GetIndexParameters().Length == 0) + { + IdGetMethod = idProperty.GetGetMethod(); + } + } + + #region IEquatable[Identity] implementation + public bool Equals(Identity other) + { + if (this.IdGetMethod != null) + { + object otherId = other.IdGetMethod.Invoke(other, null); + object thisId = this.IdGetMethod.Invoke(this, null); + return thisId.Equals(otherId); + } else + { + return base.Equals(other); + } + } + + public override bool Equals(object obj) + { + var other = obj as Identity; + return other != null && this.Equals(other); + } + + public override int GetHashCode() { - if (other == null) - return false; - - Identity _other = other as Identity; - if (_other == null) - return false; - - return this.ID == _other.ID; + if (this.IdGetMethod != null) + return this.IdGetMethod.Invoke(this, null).GetHashCode(); + else + return base.GetHashCode(); } + #endregion } } diff --git a/StudentManager/Main.cs b/StudentManager/Main.cs index 917cf93..6f1782a 100755 --- a/StudentManager/Main.cs +++ b/StudentManager/Main.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using StudentManager.TextUi; namespace StudentManager { @@ -8,6 +9,8 @@ class MainClass { public static void Main(string[] args) { + Manager m = new Manager(); + new MainScreen(m).Start(); } } } diff --git a/StudentManager/Manager.cs b/StudentManager/Manager.cs index 2f0a0af..42af193 100644 --- a/StudentManager/Manager.cs +++ b/StudentManager/Manager.cs @@ -8,9 +8,9 @@ class Manager { // Mutable collections, expected to be edited by // users. - public SortedSet Classes { get; private set; } + public HashSet Classes { get; private set; } - public SortedSet Students { get; private set; } + public HashSet Students { get; private set; } public HashSet Rooms { get; private set; } @@ -29,27 +29,54 @@ public IEnumerable> ClassStudents public IEnumerable> Allocation { get { return this.allocation; } } + /// + /// In memory constructor. All data is created fresh. Mostly for testing + /// purposes. + /// public Manager() { - Classes = new SortedSet(); - Students = new SortedSet(); + Classes = new HashSet(); + Students = new HashSet(); Rooms = new HashSet(); TimeSlots = new HashSet(); classStudents = new HashSet>(); allocation = new HashSet>(); } - public Student GetStudentById(int id) + /// + /// Initializes with external data sources. + /// + /// + /// A database accessing object. + /// + /// + /// A mapping between resources' name and their URI. The list of resources that + /// we need is: classes, students, rooms, timeslots, classstudents, allocation. + /// + public Manager(IPersistenceService database, Dictionary UriMapping) + { + Classes = new HashSet(database.load>(UriMapping["classes"])); + Students = new HashSet(database.load>(UriMapping["students"])); + } + + public Student GetStudentById(String id) { return (from student in this.Students where student.ID == id select student).SingleOrDefault(); } - public Class GetClassById(int id) +// public Class GetClassById(int id) +// { +// return (from cl in this.Classes +// where cl.ID == id +// select cl).SingleOrDefault(); +// } + + public Class GetClassByName(String name) { return (from cl in this.Classes - where cl.ID == id + where cl.Name == name select cl).SingleOrDefault(); } diff --git a/StudentManager/Student.cs b/StudentManager/Student.cs index 306119e..888ca8f 100644 --- a/StudentManager/Student.cs +++ b/StudentManager/Student.cs @@ -5,6 +5,8 @@ namespace StudentManager { public class Student: Identity { + [Identity.ID] + public string ID { get; set; } public string Name { get; set; } public string Address { get; set; } diff --git a/StudentManager/StudentManager.csproj b/StudentManager/StudentManager.csproj index 3ccaa59..23841e8 100644 --- a/StudentManager/StudentManager.csproj +++ b/StudentManager/StudentManager.csproj @@ -53,6 +53,7 @@ + \ No newline at end of file diff --git a/StudentManager/Tests/TestIdentity.cs b/StudentManager/Tests/TestIdentity.cs index abe5245..010e28c 100755 --- a/StudentManager/Tests/TestIdentity.cs +++ b/StudentManager/Tests/TestIdentity.cs @@ -14,15 +14,14 @@ public void TestStudent() { var a = new Student() { - ID = 10 + ID = "aoe" }; var b = new Student() { - ID = 5 + ID = "aoed" }; - Assert.That(a.CompareTo(b) > 0); Assert.False(a.Equals(b)); } @@ -31,15 +30,14 @@ public void TestClass() { var a = new Class() { - ID = 10 + Name = "C1203L" }; var b = new Class() { - ID = 5 + Name = "C1203F" }; - Assert.That(a.CompareTo(b) > 0); Assert.False(a.Equals(b)); } } diff --git a/StudentManager/Tests/TestManager.cs b/StudentManager/Tests/TestManager.cs index b53427d..11b6d81 100755 --- a/StudentManager/Tests/TestManager.cs +++ b/StudentManager/Tests/TestManager.cs @@ -12,12 +12,11 @@ class TestStudentManagement private Manager m; private Student trung = new Student() { - ID = 10, + ID = "B01414", Name = "Trung" }; private Class c1203l = new Class() { - ID = 3, Name = "C1203L", Teacher = "NhatNK" }; @@ -29,11 +28,11 @@ public void Init() { [Test] public void TestGetStudentByID() { - Assert.Null(m.GetStudentById(10)); + Assert.Null(m.GetStudentById("B01415")); m.Students.Add(trung); - Assert.AreSame(trung, m.GetStudentById(10)); - Assert.Null(m.GetStudentById(80)); + Assert.AreSame(trung, m.GetStudentById("B01414")); + Assert.Null(m.GetStudentById("")); } [Test] @@ -53,8 +52,7 @@ public void TestRegisterStudent() { [Test] public void TestChangeClassOfStudent() { var bogusClass = new Class() - { - ID = 40, + { Name = "Bogus" }; m.Classes.Add(c1203l); @@ -75,12 +73,10 @@ class TestClassAllocation private Manager m; private Class c1203l = new Class() { - ID = 3, Name = "C1203L" }; private Class bogus = new Class() { - ID = 4, Name = "Bogus" }; diff --git a/StudentManager/Tests/TestPersistence.cs b/StudentManager/Tests/TestPersistence.cs index 65baf13..1c4efea 100644 --- a/StudentManager/Tests/TestPersistence.cs +++ b/StudentManager/Tests/TestPersistence.cs @@ -34,7 +34,7 @@ public void SaveSingleObject() { var s = new Student() { - ID = 10, + ID = "B01414", Name = "Trung", Address = "Home" }; @@ -43,7 +43,7 @@ public void SaveSingleObject() Assert.AreEqual( @" - 10 + B01414 Trung
Home
", @@ -56,7 +56,6 @@ public void SaveSortedSet() { var s = new SortedSet(); s.Add(new Class() { - ID = 1, Name = "C1203L", Teacher = "NhatNK" }); @@ -67,7 +66,7 @@ public void SaveSortedSet() public void LoadIdentity() { var s = new Student() { - ID = 10, + ID = "B01414", Name = "Trung", Address = "Home" }; @@ -82,7 +81,6 @@ public void LoadCollection() { var expected = new SortedSet(); expected.Add(new Class() { - ID = 1, Name = "C1203L", Teacher = "NhatNK" }); diff --git a/StudentManager/TextInterface.cs b/StudentManager/TextInterface.cs new file mode 100644 index 0000000..6609464 --- /dev/null +++ b/StudentManager/TextInterface.cs @@ -0,0 +1,223 @@ +// +// TextInterface.cs +// +// Author: +// chin <${AuthorEmail}> +// +// Copyright (c) 2013 chin +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +using System; +using System.Linq; +using System.Collections.Specialized; +using System.Collections; + +namespace StudentManager.TextUi +{ + abstract class ChoiceScreen + { + private OrderedDictionary commands; + private bool running = false; + + protected event Action PreMenuHook; + protected event Action PreActionHook; + protected event Action PostActionHook; + + protected ChoiceScreen Parent; + + public ChoiceScreen(ChoiceScreen parent) + { + this.Parent = parent; + commands = new OrderedDictionary(); + } + + public void AddChoice(String key, String label, Action action) + { + commands.Add(key, new Tuple(label, action)); + } + + public virtual void Start() + { + this.running = true; + while (this.running) + { + if (PreMenuHook != null) + PreMenuHook(); + foreach (DictionaryEntry de in commands) + { + Console.WriteLine( + String.Format("{0} - {1}", + de.Key, + (de.Value as Tuple).Item1)); + } + + String choice = System.Console.ReadLine(); + // FIXME This implementation does not allow key aliases + // i.e. using both "b"(ack) and "q"(uit) for the + // quit action on the main screen. + if (commands.Contains(choice)) + { + if (PreActionHook != null) + PreActionHook(); + + (commands[choice] as Tuple).Item2.Invoke(); + + if (PostActionHook != null) + PostActionHook(); + } else + { + Console.WriteLine("No such choice!"); + } + } + } + + /// + /// Stop this instance and return to parent screen. + /// + public virtual void Stop() + { + this.running = false; + } + + /// + /// Stop this instance and chain all parents' Quit method. + /// + public void Quit() + { + this.Stop(); + if (Parent != null) + Parent.Quit(); + } + } + + class MainScreen : ChoiceScreen + { + Manager manager; + + public MainScreen(Manager manager): base(null) + { + this.manager = manager; + + AddChoice("1", "Manage classes", ManageClasses); + AddChoice("2", "Manage timetable", ManageTimetable); + AddChoice("q", "Quit", Quit); + } + + public void ManageClasses() + { + new ClassScreen(this.manager, this).Start(); + } + + public void ManageTimetable() + { + + } + + public override void Stop() + { + base.Stop(); + } + } + + class ClassScreen : ChoiceScreen + { + Manager manager; + + public ClassScreen(Manager manager, ChoiceScreen parent): + base(parent) + { + this.manager = manager; + + AddChoice("1", "Add a class", AddClass); + AddChoice("2", "Select a class to edit", SelectClass); + AddChoice("b", "Back", Stop); + AddChoice("q", "Quit", Quit); + + this.PreMenuHook += () => { + foreach (var cl in manager.Classes) + { + Console.WriteLine(String.Format("{0} {1}", cl.Name, cl.Teacher)); + } + }; + } + + public void AddClass() + { + Console.Write("Class name: "); + String name = Console.ReadLine(); + + Console.Write("Teacher: "); + String teacher = Console.ReadLine(); + + var c = new Class() + { + Name = name, + Teacher = teacher + }; + + manager.Classes.Add(c); + } + + class EachClassScreen : ChoiceScreen + { + Class c; + ClassScreen parent; + + public EachClassScreen(Class c, ClassScreen parent): + base(parent) + { + this.c = c; + this.parent = parent; + + AddChoice("1", "Select student ID", SelectStudent); + AddChoice("2", "Remove class", RemoveClass); + AddChoice("3", "Change class info", ChangeClassInfo); + AddChoice("b", "Back", Stop); + AddChoice("q", "Quit", Quit); + + this.PreMenuHook += () => { + var studentList = + (from tuple in this.parent.manager.ClassStudents + where tuple.Item1.Equals(this.c) + select tuple.Item2); + + foreach (Student student in studentList) + { + Console.WriteLine(String.Format("{0} {1}", student.ID, student.Name)); + } + }; + } + + void SelectStudent() + { + } + + void RemoveClass() + { + } + + void ChangeClassInfo() {} + } + + public void SelectClass() + { + Console.Write("Select a class ID: "); + String name = Console.ReadLine(); + Class c = manager.GetClassByName(name); + new EachClassScreen(c, this).Start(); + } + } +} + diff --git a/StudentManager/XMLDatabase.cs b/StudentManager/XMLDatabase.cs index b68eab4..f66de86 100644 --- a/StudentManager/XMLDatabase.cs +++ b/StudentManager/XMLDatabase.cs @@ -25,21 +25,27 @@ namespace StudentManager { - public class XMLDatabase + public interface IPersistenceService { - public void save(String path, T data) + void save(String uri, T data); + T load(String uri); + } + + public class XMLDatabase : IPersistenceService + { + public void save(String uri, T data) { var serializer = new XmlSerializer(typeof(T)); - using (var textWriter = new StreamWriter(path)) { + using (var textWriter = new StreamWriter(uri)) { serializer.Serialize(textWriter, data); } } - public T load(String path) + public T load(String uri) { T data; var serializer = new XmlSerializer(typeof(T)); - using (var textReader = new StreamReader(path)) { + using (var textReader = new StreamReader(uri)) { data = (T) serializer.Deserialize(textReader); } return data;