using System; using System.Collections.Generic; using System.ComponentModel; using System.Collections; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Text; using miew.ReadOnly; using miew.Binding; using miew.Reflection; using agree; using System.Collections.ObjectModel; namespace agree.itsdb { using Type = System.Type; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public interface IitsdbTable : IBindingList, ISysObj { Type ItsdbType { get; } void Load(String s_dir, IList<String> schema); void ValidateExternalKeys(); }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class ItsdbDatabase : List<IitsdbTable>, ISysObj { readonly static public Type[] ttypes = { typeof(ItsdbItemTable), typeof(ItsdbAnalysisTable), typeof(ItsdbPhenomenonTable), typeof(ItsdbParameterTable), typeof(ItsdbSetTable), typeof(ItsdbItem_PhenomenonTable), typeof(ItsdbItem_SetTable), typeof(ItsdbRunTable), typeof(ItsdbParseTable), typeof(ItsdbResultTable), typeof(ItsdbRuleTable), typeof(ItsdbOutputTable), typeof(ItsdbEdgeTable), typeof(ItsdbTreeTable), typeof(ItsdbDecisionTable), typeof(ItsdbPreferenceTable), typeof(ItsdbUpdateTable), typeof(ItsdbFoldTable), typeof(ItsdbScoreTable), }; public Dictionary<String, IitsdbTable> table_name_lookup = new Dictionary<String, IitsdbTable>(StringComparer.OrdinalIgnoreCase); public IList<IitsdbTable> Tables { get { return this; } } public ItsdbItemTable ItemTable { get { return (ItsdbItemTable)table_name_lookup["item"]; } } public ItsdbAnalysisTable AnalysisTable { get { return (ItsdbAnalysisTable)table_name_lookup["analysis"]; } } public ItsdbParseTable ParseTable { get { return (ItsdbParseTable)table_name_lookup["parse"]; } } public ItsdbResultTable ResultsTable { get { return (ItsdbResultTable)table_name_lookup["result"]; } } public ItsdbRunTable RunTable { get { return (ItsdbRunTable)table_name_lookup["run"]; } } public String SourceDirectory { get { return s_dir; } } String s_dir; ISysObj so; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public IitsdbTable TableFromAnonymousItems(String name, String description, IEnumerable data) { Object prototype = data.OfType<Object>().First(); var pi = AnonymousTypePromoter.GetPromotionInfo( name, prototype, new String[] { "System.ComponentModel", "agree.itsdb" }, new String[] { "System.dll", Assembly.GetAssembly(typeof(ItsdbItemType)).Location }, new Type[] { typeof(ItsdbItemType) }); Type T_tab = typeof(ItsdbTable<>).MakeGenericType(new Type[] { pi.Type }); IitsdbTable t = (IitsdbTable)Activator.CreateInstance(T_tab, this, name, description); foreach (Object o in data) t.Add(pi.Promote(o)); this.Add(t); return t; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Construct an Itsdb database over the specified directory /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public ItsdbDatabase(ISysObj so, String s_dir) :base(ttypes.Length) { this.so = so; this.s_dir = s_dir; Object[] ctor_args = new Object[] { this }; for (int i = 0; i < ttypes.Length; i++) { IitsdbTable t = (IitsdbTable)Activator.CreateInstance(ttypes[i], ctor_args); this.Add(t); table_name_lookup.Add(t.SysObjName, t); } String relations_file = Path.Combine(s_dir, "relations"); if (!File.Exists(relations_file)) { String msg = String.Format("The filename 'relations' could not be found in the directory '{0}'", s_dir); throw new FileNotFoundException(msg, "relations"); } List<String> schema = new List<String>(); IitsdbTable cur = null; foreach (String _l in File.ReadAllLines(relations_file)) { String l = _l; int ix = l.IndexOf('#'); if (ix != -1) l = l.Remove(ix); l = l.Trim(); if (l == String.Empty) continue; String[] rgs = l.Split(default(Char[]), StringSplitOptions.RemoveEmptyEntries); if (rgs.Length == 1) { if (cur != null) { cur.Load(s_dir, schema); schema.Clear(); } String s_tab = rgs[0].Trim(':'); if (!table_name_lookup.TryGetValue(s_tab, out cur)) { String msg = String.Format("The relations file refers to an unknown table type '{0}'", s_tab); throw new Exception(msg); } } else schema.Add(rgs[0]); } /// do the last table if (cur != null) cur.Load(s_dir, schema); /// now that all tables are loaded, validate external keys //foreach (IitsdbTable t in tables) // t.ValidateExternalKeys(); } public IitsdbTable GetTableFromItemType(Type t) { return this.First(tt => tt.ItsdbType == t); } public string SysObjName { get { return s_dir; } } public string SysObjDescription { get { return String.Format("[incr tsdb()] database : {0}", s_dir); } } public IReadOnlyDictionary<String, ISysObj> SysObjChildren { get { return new CovariantDictionaryWrapper<String, ISysObj, IitsdbTable>(table_name_lookup); } } public ISysObj SysObjParent { get { return so; } } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///// <summary> ///// ///// </summary> ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //public class ItsdbJoinedTable<T> : ListItemBindHelper<T>, IitsdbJoinedTable //{ // ItsdbDatabase db; // String name; // String description; // Type T_item; // public ItsdbJoinedTable(ItsdbDatabase db, String name, String description, IEnumerable<T> data) // :base(data) // { // //data.OfType<Object>().ToList() // this.db = db; // this.name = name; // this.description = description; // //this.T_item = this.Count > 0 ? this[0].GetType() : typeof(Object); // } // public string SysObjName // { // get { return name; } // } // public string SysObjDescription // { // get { return description; } // } // public IReadOnlyDictionary<String, ISysObj> SysObjChildren // { // get { return SysObjHelper<ISysObj>.Empty; } // } // public ISysObj SysObjParent // { // get { return db; } // } // public Type ItsdbType // { // get { return T_item; } // } //}; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Template class for table of Itsb items of some strong-type /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class ItsdbTable<T> : BindingList<T>, IitsdbTable where T : ItsdbItemType { public ItsdbDatabase db; String name; String description; public ItsdbTable(ItsdbDatabase db) { this.db = db; } public ItsdbTable(ItsdbDatabase db, String name, String description) :this(db) { this.name = name; this.description = description; } public Type ItsdbType { get { return typeof(T); } } /// <summary> /// Using the ordered list of fields specified in 'schema,' load the table with data from a plaintext or gzip /// file with the table's matching name, if any is found in the specified directory. /// </summary> public void Load(String s_dir, IList<String> schema) { var fields = schema.Select(s => { FieldInfo fi = typeof(T).GetField("_" + s.Replace('-', '_'), BindingFlags.Instance | BindingFlags.NonPublic); if (fi == null) { String msg = String.Format("The field '{0}' in the schema for '{1}' is not recognized.", s, SysObjName); throw new Exception(msg); } return fi; }).ToArray(); /// See if there's a data file for this table String db_file = Path.Combine(s_dir, SysObjName); Stream str; if (!File.Exists(db_file)) { db_file += ".gz"; if (!File.Exists(db_file)) return; } /// If the data file is compressed, switch the stream to a gzip decoder str = File.Open(db_file, FileMode.Open, FileAccess.Read, FileShare.Read); if (db_file.EndsWith(".gz")) str = new GZipStream(str, CompressionMode.Decompress); /// Read lines from the data file into the table bool f_gave_warning = false; using (StreamReader sr = new StreamReader(str, Encoding.UTF8)) { int i_l = 0; String line; while ((line = sr.ReadLine()) != null) { i_l++; /// Split the line of raw data into parts String[] data = line.Replace(@"\\", @"\").Split('@'); if (data.Length > fields.Length) { String msg = String.Format("Number of data items does not match schema in file '{0}', line {1}", db_file, i_l); throw new Exception(msg); } else if (!f_gave_warning && data.Length < fields.Length) { //Console.WriteLine("warning: Number of data fields ({0}) is less than the number of fields in the schema ({1}) in file '{2}'", data.Length, fields.Length, db_file); f_gave_warning = true; } /// Create an item of the appropriate type and load it with the data from each field T o_item = Activator.CreateInstance<T>(); for (int i = 0; i < data.Length; i++) { FieldInfo fi = fields[i]; String d = data[i]; /// Convert the string data into a strongly typed object of the appropriate type Object o_data; if (fi.FieldType == typeof(long)) { long il; if (d == String.Empty) il = 0; else if (!long.TryParse(d, out il)) { String msg = String.Format("Invalid long integer value '{0}' in file '{1}', line {2}", d, db_file, i_l); throw new Exception(msg); } o_data = il; } else if (fi.FieldType == typeof(int)) { int ii; if (d == String.Empty) ii = 0; else if (!int.TryParse(d, out ii)) { uint ui; if (!uint.TryParse(d, out ui)) { String msg = String.Format("Invalid integer value '{0}' in file '{1}', line {2}", d, db_file, i_l); throw new Exception(msg); } ii = (int)ui; } o_data = ii; } else if (fi.FieldType == typeof(string)) o_data = d; else if (fi.FieldType == typeof(DateTime)) { String sd = new String(d.Where(ch => ch != '(' && ch != ')').ToArray()); DateTime dt; if (sd == String.Empty) dt = default(DateTime); else if (!DateTime.TryParse(sd, CultureInfo.GetCultureInfo("de-DE"), DateTimeStyles.None, out dt)) { String msg = String.Format("Unrecognized date/time format '{0}' in file '{1}', line {2}", d, db_file, i_l); throw new Exception(msg); } o_data = dt; } else throw new Exception(); /// Set the field's value into the item fi.SetValue(o_item, o_data); } /// Add the item to the table this.Add(o_item); } } str.Dispose(); /// setup primary keys, if any foreach (FieldInfo pkf in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic) .Where(fi => fi.GetCustomAttributes(true).Any(o => o is PrimaryKeyAttribute))) { if (pkf.FieldType != typeof(int)) throw new Exception("primary key must be an integer"); FieldInfo map_field = GetType().GetField(pkf.Name + "_map"); Dictionary<int, T> map = (Dictionary<int, T>)map_field.GetValue(this); #if DUPLICATE_KEY_INFO Dictionary<int, int> repeated_keys = new Dictionary<int, int>(); #endif foreach (T t in this) { int id = (int)pkf.GetValue(t); if (map.ContainsKey(id)) { #if DUPLICATE_KEY_INFO if (repeated_keys.ContainsKey(id)) repeated_keys[id]++; else repeated_keys.Add(id, 2); #endif } else map.Add(id, t); } #if DUPLICATE_KEY_INFO if (repeated_keys.Count > 0) { Console.WriteLine("warning: field '{0}' in table '{1}' has duplicate primary key value(s):", pkf.Name, db_file); foreach (var kvp in repeated_keys.OrderBy(k => k.Key)) Console.WriteLine("\tvalue: {0} ({1} instances)", kvp.Key, kvp.Value); } #endif } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// validate external keys via reflection /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void ValidateExternalKeys() { /// Process all fields in this table's item type which are marked with the 'ExternalKey' attribute foreach (var fa in typeof(T) .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) .SelectMany(fi_this => fi_this.GetCustomAttributes(true) .OfType<ExternalKeyAttribute>() .Select(attr => new { fi_this, attr }))) { /// Common name of the shared key String s_key = fa.fi_this.Name; /// type of external item which introduces the primary key Type ext_item_type = fa.attr.ti_ext; /// find a matching field name in the external item FieldInfo fi_ext = ext_item_type.GetField(s_key, BindingFlags.Instance | BindingFlags.NonPublic); if (fi_ext == null || fi_ext.GetCustomAttributes(typeof(PrimaryKeyAttribute), true).Length == 0) { String msg = String.Format("Couldn't resolve primary key '{0}' in table '{1}' referenced by external key in table '{2}'", s_key, ext_item_type, SysObjName); throw new Exception(msg); } /// get external table IitsdbTable ext_table = db.GetTableFromItemType(ext_item_type) as IitsdbTable; if (ext_table == null) continue; /// get external table type Type ext_table_type = ext_table.GetType(); /// Get the map out of the external table Object map = ext_table_type.GetField(s_key + "_map", BindingFlags.Instance | BindingFlags.NonPublic) .GetValue(ext_table); /// find the 'ContainsKey' method for a generic dictionary containing the external item as a value MethodInfo mi_ContainsKey = typeof(Dictionary<,>) .MakeGenericType(new Type[] { typeof(int), ext_item_type }) .GetMethod("ContainsKey"); /// Check each item in this table HashSet<int> bad_ids = new HashSet<int>(); foreach (T t in this) { /// get the secondary key value specified by an item int id = (int)fa.fi_this.GetValue(t); /// See if the primary contains the value if (!(bool)mi_ContainsKey.Invoke(map, new Object[] { id })) bad_ids.Add(id); } if (bad_ids.Count > 0) { Console.WriteLine(); String msg = String.Format("The following value(s) specified for field '{0}' in table '{1}' do not match any value of that field in primary key table '{2}':", s_key.Substring(1).Replace('_', '-'), SysObjName, ext_table.SysObjName); Console.WriteLine(msg); Console.WriteLine(String.Join(", ", bad_ids.OrderBy(_i => _i))); } } } public String SysObjName { get { return typeof(T).Name.Replace("Itsdb", String.Empty).Replace('_', '-').ToLower(); } } public string SysObjDescription { get { return String.Format("{0} - {1}", db.SysObjName, SysObjName); } } public IReadOnlyDictionary<string, ISysObj> SysObjChildren { get { return SysObjHelper<ISysObj>.Empty; } } public ISysObj SysObjParent { get { return db; } } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Itsdb table types follow /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class ItsdbItemTable : ItsdbTable<ItsdbItem> { public ItsdbItemTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbItem> _i_id_map = new Dictionary<int, ItsdbItem>(); }; public class ItsdbAnalysisTable : ItsdbTable<ItsdbAnalysis> { public ItsdbAnalysisTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbPhenomenonTable : ItsdbTable<ItsdbPhenomenon> { public ItsdbPhenomenonTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbPhenomenon> _p_id_map = new Dictionary<int, ItsdbPhenomenon>(); }; public class ItsdbParameterTable : ItsdbTable<ItsdbParameter> { public ItsdbParameterTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbSetTable : ItsdbTable<ItsdbSet> { public ItsdbSetTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbSet> _s_id_map = new Dictionary<int, ItsdbSet>(); }; public class ItsdbItem_PhenomenonTable : ItsdbTable<ItsdbItem_Phenomenon> { public ItsdbItem_PhenomenonTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbItem_Phenomenon> _ip_id_map = new Dictionary<int, ItsdbItem_Phenomenon>(); }; public class ItsdbItem_SetTable : ItsdbTable<ItsdbItem_Set> { public ItsdbItem_SetTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbRunTable : ItsdbTable<ItsdbRun> { public ItsdbRunTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbRun> _run_id_map = new Dictionary<int, ItsdbRun>(); }; public class ItsdbParseTable : ItsdbTable<ItsdbParse> { public ItsdbParseTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbParse> _parse_id_map = new Dictionary<int, ItsdbParse>(); }; public class ItsdbResultTable : ItsdbTable<ItsdbResult> { public ItsdbResultTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbResult> _result_id_map = new Dictionary<int, ItsdbResult>(); }; public class ItsdbRuleTable : ItsdbTable<ItsdbRule> { public ItsdbRuleTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbOutputTable : ItsdbTable<ItsdbOutput> { public ItsdbOutputTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbEdgeTable : ItsdbTable<ItsdbEdge> { public ItsdbEdgeTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbEdge> _e_id_map = new Dictionary<int, ItsdbEdge>(); }; public class ItsdbTreeTable : ItsdbTable<ItsdbTree> { public ItsdbTreeTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbDecisionTable : ItsdbTable<ItsdbDecision> { public ItsdbDecisionTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbPreferenceTable : ItsdbTable<ItsdbPreference> { public ItsdbPreferenceTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbUpdateTable : ItsdbTable<ItsdbUpdate> { public ItsdbUpdateTable(ItsdbDatabase db) : base(db) { } }; public class ItsdbFoldTable : ItsdbTable<ItsdbFold> { public ItsdbFoldTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbFold> _f_id_map = new Dictionary<int, ItsdbFold>(); }; public class ItsdbScoreTable : ItsdbTable<ItsdbScore> { public ItsdbScoreTable(ItsdbDatabase db) : base(db) { } public Dictionary<int, ItsdbScore> _score_id_map = new Dictionary<int, ItsdbScore>(); }; }