*** empty log message ***
[m17n/m17n-lib-cs.git] / MDatabase.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.IO;
5 using System.Xml;
6
7 using M17N;
8 using M17N.Core;
9
10 namespace M17N.Core
11 {
12   internal class MGlob
13   {
14     static readonly char sep = Path.DirectorySeparatorChar;
15
16     public static void FileList (ref List<FileInfo> files,
17                                  string dir, string pattern)
18     {
19       int len = pattern.Length;
20
21       if (Path.IsPathRooted (pattern))
22         {
23           int headsep = 0;
24           int i;
25
26           for (i = 1; i < len && (pattern[i] != '*' && pattern[i] != '?'); i++)
27             if (pattern[i] == sep)
28               headsep = i;
29           if (i == len)
30             {
31               if (File.Exists (pattern))
32                 files.Add (new FileInfo (pattern));
33               return;
34             }
35           dir = pattern.Substring (0, headsep);
36           pattern = pattern.Substring (headsep + 1);
37         }
38       else
39         {
40           if (dir == null)
41             dir = Directory.GetCurrentDirectory ();
42         }
43       if (Directory.Exists (dir))
44         list (ref files, new DirectoryInfo (dir), pattern);
45     }
46
47     private static void list (ref List<FileInfo> files,
48                               DirectoryInfo dirinfo, string pattern)
49     {
50       int len = pattern.Length;
51       int i;
52     
53       M17n.DebugPrint ("Listing {0} in {1} ...", pattern, dirinfo);
54       for (i = 0; i < len && pattern[i] != sep; i++);
55       try {
56         if (i == len)
57           {
58             FileInfo[] listing = dirinfo.GetFiles (pattern);
59             i = listing.Length;
60             foreach (FileInfo elt in listing)
61               files.Add (elt);
62           }
63         else
64           {
65             string tail = pattern.Substring (i + 1);
66             pattern = pattern.Substring (0, i);
67             DirectoryInfo[] listing = dirinfo.GetDirectories (pattern);
68
69             i = listing.Length;
70             foreach (DirectoryInfo elt in listing)
71               list (ref files, elt, tail);
72           }
73       } catch {
74       }
75       M17n.DebugPrint (" found {0} files\n", i);
76     }
77   }
78
79   internal class MDatabaseDir
80   {
81     private const string ListFileName = "mdb.dir";
82
83     public string Dirname;
84     public DirectoryInfo DirInfo;
85     public DateTime DirChangeTime;
86     public FileInfo ListInfo;
87     public DateTime ListChangeTime;
88
89     public MDatabaseDir (string dirname)
90     {
91       Dirname = dirname;
92       DirChangeTime = ListChangeTime = DateTime.Now;
93     }
94
95     public void Refresh ()
96     {
97       if (DirInfo != null)
98         {
99           if (Dirname != null && Directory.Exists (Dirname))
100             {
101               DirInfo.Refresh ();
102               if (DirChangeTime < DirInfo.LastWriteTime)
103                 DirChangeTime = DirInfo.LastWriteTime;
104             }
105           else
106             {
107               DirInfo = null;
108               DirChangeTime = DateTime.Now;
109             }
110         }
111       else
112         {
113           if (Dirname != null && Directory.Exists (Dirname))
114             {
115               DirInfo = new DirectoryInfo (Dirname);
116               DirChangeTime = DateTime.Now;
117             }
118         }
119       if (DirInfo == null)
120         {
121           if (ListInfo != null)
122             {
123               ListInfo = null;
124               ListChangeTime = DateTime.Now;
125             }
126         }
127       else
128         {
129           if (ListInfo != null)
130             {
131               ListInfo.Refresh ();
132               if (ListChangeTime < ListInfo.LastWriteTime)
133                 ListChangeTime = ListInfo.LastWriteTime;
134             }
135           else
136             {
137               try {
138                 ListInfo = DirInfo.GetFiles (ListFileName)[0];
139                 ListChangeTime = DateTime.Now;
140               } catch {
141               }
142             }
143         }
144     }
145   }
146
147   public partial class MDatabase : IComparable<MDatabase>
148   {
149     /// Identifier of a MDatabase.
150     public struct Tag : IEquatable<Tag>
151     {
152       private MSymbol[] Tags;
153
154       public Tag (MSymbol tag0)
155         {
156           Tags = new MSymbol[4];
157           Tags[0] = tag0; Tags[1] = Tags[2] = Tags[3] = MSymbol.nil;
158         }
159
160       public Tag (MSymbol tag0, MSymbol tag1)
161         {
162           Tags = new MSymbol[4];
163           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = Tags[3] = MSymbol.nil;
164         }
165
166       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2)
167         {
168           Tags = new MSymbol[4];
169           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = MSymbol.nil;
170         }
171
172       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
173         {
174           Tags = new MSymbol[4];
175           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = tag3;
176         }
177
178       public Tag (ref MPlist plist)
179         {
180           Tags = new MSymbol[4];
181
182           for (int i = 0; i < 4; i++)
183             {
184               if (plist.IsSymbol)
185                 {
186                   Tags[i] = plist.Symbol;
187                   plist = plist.Next;
188                 }
189               else
190                 Tags[i] = MSymbol.nil;
191             }
192         }
193
194       public bool Equals (Tag tag)
195       {
196         for (int i = 0; i < 4; i++)
197           if (tag[i] != Tags[i])
198             return false;
199         return true;
200       }
201
202       public override int GetHashCode ()
203       {
204         return (Tags[0].GetHashCode () ^ Tags[1].GetHashCode ()
205                 ^ Tags[2].GetHashCode () ^ Tags[3].GetHashCode ());
206       }
207
208       public override string ToString ()
209       {
210         return ("<"
211                 + Tags[0] + "," + Tags[1] + "," + Tags[2] + "," + Tags[3]
212                 + ">");
213       }
214
215       public MSymbol this[int i]
216       {
217         set { Tags[i] = value; }
218         get { return Tags[i]; }
219       }
220
221       public bool Match (Tag tag)
222       {
223         for (int i = 0; i < 4; i++)
224           {
225             if (tag[i] == Mwildcard || Tags[i] == Mwildcard)
226               return true;
227             if (tag[i] != Tags[i])
228               return false;
229           }
230         return true;
231       }
232
233       public bool HasWildcard {
234         get {
235           for (int i = 0; i < 4; i++)
236             if (Tags[i] == Mwildcard)
237               return true;
238           return false;
239         }
240       }
241     }
242
243     public delegate object Loader (Tag tag, object extra_info);
244
245     internal class MDatabaseInfo
246     {
247       // These come from the declartion plist of the database.
248       internal string Description;
249       internal string Filename;
250       internal FileInfo Validater;
251       internal int Version;
252       internal MSymbol Format;
253       internal MSymbol Schema;
254       internal string SchemaFile;
255       internal MPlist Props;
256
257       public MDatabaseInfo ()
258       {
259         Format = Schema = MSymbol.nil;
260       }
261
262       private static int parse_version (MPlist plist)
263       {
264         string[] str;
265         int major, minor, release;
266
267         if (! plist.IsMText)
268           return 0xFFFFFF;
269         str = plist.Text.ToString ().Split ('.');
270         if (str.Length != 3)
271           return 0xFFFFFF;
272         try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; }
273         try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; }
274         try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; }
275         return ((major << 16) | (minor << 8) | release);
276       }
277
278       public MDatabaseInfo (MPlist plist)
279       {
280         Format = MSymbol.nil;
281         if (plist.IsMText)
282           {
283             Format = MSymbol.plist;
284             Filename = plist.Text.ToString ();
285             plist = plist.Next;
286           }
287         else if (plist.IsPlist)
288           {
289             MPlist p = plist.Plist;
290
291             if (p.IsMText)
292               {
293                 Filename = p.Text.ToString ();
294                 p = p.Next;
295               }
296             if (p.IsSymbol)
297               {
298                 Format = p.Symbol;
299                 p = p.Next;
300               }
301             if (p.IsSymbol)
302               {
303                 Schema = p.Symbol;
304                 p = p.Next;
305               }
306             if (p.IsMText)
307               SchemaFile = p.Text.ToString ();
308             plist = plist.Next;
309           }
310
311         Version = 0;
312         Props = new MPlist ();
313         foreach (MPlist pl in plist)
314           {
315             if (pl.IsPlist)
316               {
317                 MPlist p = pl.Plist;
318               
319                 if (p.IsSymbol && p.Symbol == Mversion)
320                   Version = parse_version (p.Next);
321                 else
322                   Props.Put (pl.Key, pl.Val);
323               }
324             else if (pl.IsSymbol)
325               {
326                 MPlist p = new MPlist ();
327                 p.Add (MSymbol.symbol, pl.Symbol);
328                 p.Add (MSymbol.symbol, MSymbol.t);
329                 Props.Put (MSymbol.plist, p);
330               }
331           }
332       }
333
334       public void Merge (MDatabaseInfo src)
335       {
336         if (Validater == null)
337           Validater = src.Validater;
338         if (Version == 0)
339           Version = src.Version;
340         if (Format == MSymbol.nil)
341           Format = src.Format;
342         if (Schema == MSymbol.nil)
343           Schema = src.Schema;
344         if (SchemaFile == null)
345           SchemaFile = src.SchemaFile;
346         foreach (MPlist p in src.Props)
347           if (Props.Assq (p.Plist.Symbol) == null)
348             Props.Push (p.Key, p.Val);
349       }
350
351       public override string ToString ()
352       {
353         string str = ("#<Info " + Format + " \"" + Filename + "\"");
354         if (Schema != MSymbol.nil)
355           str += " " + Schema;
356         return str + ">";
357       }
358     }
359
360     // Dictionaries for normal databases.
361     private static Dictionary<MDatabase.Tag, List<MDatabase>> ndict
362       = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
363
364     // Dictionaries for databases of DBType WILDCARD
365     private static Dictionary<MDatabase.Tag, List<MDatabase>> wdict
366       = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
367
368     private static MDatabaseDir[] DBDirs = new MDatabaseDir[4];
369
370     private static DateTime LastUpdateTime = new DateTime (0);
371
372     private static readonly MSymbol Mversion = "version";
373     private static readonly MSymbol Mwildcard = "*";
374     private static readonly MSymbol Mchar_table = "char-table";
375     private static readonly MSymbol Mcharset = "charset";
376     private static readonly MSymbol Mxml = "xml";
377
378     private static TimeSpan CheckInterval = new TimeSpan (50000000);
379
380     /// Type of database
381     private enum MDBType
382       {
383         /// The database was defined automatically from one of mdb.dir
384         /// files with no wildcard tag.
385         AUTO,
386         /// The database was defined automatically from one of mdb.dir
387         /// files with a wildcard tag to define multiple databases
388         /// of the same kind.
389         MULTIPLE,
390         /// The database was defined explicitely by MDatabase.Define
391         /// to use the normal loader.
392         EXPLICIT,
393         /// The database was defined explicitely by MDatabase.Define
394         /// to use a special loader.
395         UNKNOWN,
396         /// The database is for defining multiple databases of the
397         /// same kind with a wildcard tag.
398         WILDCARD,
399       };
400
401     /// Status of database
402     private enum MDBStatus
403       {
404         // The database file has not yet been decided, or is not yet
405         // expanded if DBType is WILDCARD.
406         NOT_READY,
407         // The database file was decided, or has been expanded if
408         // DBType is WILDCARD.
409         READY,
410         // The database is disabled.  It means that the database file
411         // is not readable, the version is not supported by the
412         // current system, or the validation was failed.
413         DISABLED,
414         // The database is deleted by the modificaiton of mdb.dir, or
415         // is overwritten by a new explicit definition.
416         INVALID,
417       };
418
419     public Tag tag;
420     private Loader loader;
421     private object ExtraInfo;
422     // Directory of the database file.
423     // -1:unknown, 0:absolute, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
424     private int DirIndex;
425     // Directory of the mdb.dir defining the database file.
426     // 0: EXPLICIT or UNKNOWN, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
427     private int ListIndex;
428     private MDBType DBType;
429     private MDBStatus DBStatus;
430     internal MDatabaseInfo Info;
431     // File in which the database contents is stored.  This is null
432     // when DBStatus is NOT_READY.
433     internal FileInfo FileInfo;
434     // When the database file is loaded last.
435     internal DateTime LastLoaded = DateTime.Now;
436
437     public enum LoadStatus
438     {
439       None,
440       InvalidLoadMethod,
441       NotAvailable,
442       NotReadable,
443       InvalidContents,
444     };
445
446     public LoadStatus LastLoadStatus = LoadStatus.None;
447
448     static MDatabase ()
449     {
450       string share_dir = (Environment.GetFolderPath
451                           (Environment.SpecialFolder.CommonApplicationData));
452       string usr_dir = (Environment.GetFolderPath
453                         (Environment.SpecialFolder.ApplicationData));
454
455       try {
456         string dir = Environment.GetEnvironmentVariable ("M17NDIR");
457         DBDirs[1] = new MDatabaseDir (dir);
458       } catch {
459         try {
460           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
461         } catch (ArgumentException) {
462           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
463         }
464       }
465       DBDirs[2] = new MDatabaseDir (null);
466       DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
467       update_all (true);
468     }
469
470     public static string ApplicationDir
471     {
472       get { return (DBDirs[1].Dirname); }
473       set { DBDirs[2] = new MDatabaseDir (value); update_all (true); }
474     }
475
476     // Update all listing and directories.  Return true iff some are
477     // really updated.
478     private static bool update_all (bool force)
479     {
480       if (! force && DateTime.Now - LastUpdateTime < CheckInterval)
481         return false;
482
483       bool updated = false;
484       for (int i = 1; i < 4; i++)
485         if (DBDirs[i].Dirname != null)
486           {
487             DBDirs[i].Refresh ();
488             if (LastUpdateTime < DBDirs[i].ListChangeTime)
489               {
490                 update_list (i);
491                 updated = true;
492               }
493             if (LastUpdateTime < DBDirs[i].DirChangeTime)
494               {
495                 update_dir (i);
496                 updated = true;
497               }
498           }
499       if (updated)
500         LastUpdateTime = DateTime.Now;
501       return updated;
502     }
503
504     public static void Dump ()
505     {
506       update_all (false);
507       Console.WriteLine ("[DBDirs]");
508       for (int i = 1; i < 4; i++)
509         if (DBDirs[i].Dirname != null)
510           {
511             if (DBDirs[i].DirInfo != null)
512               {
513                 Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName);
514                 if (DBDirs[i].ListInfo != null)
515                   Console.WriteLine (" {0}", DBDirs[i].ListInfo);
516                 else
517                   Console.WriteLine (" .. no mdb.dir");
518               }
519             else
520               Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname);
521           }
522
523       Console.WriteLine ("[WDICT]");
524       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
525         foreach (MDatabase mdb in kv.Value)
526           Console.WriteLine (mdb);
527
528       Console.WriteLine ("[NDICT]");
529       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
530         foreach (MDatabase mdb in kv.Value)
531           Console.WriteLine (mdb);
532     }
533
534     private static void register (MDatabase mdb)
535     {
536       Dictionary<MDatabase.Tag, List<MDatabase>> dict
537         = mdb.DBType == MDBType.WILDCARD ? wdict : ndict;
538       List<MDatabase> mdbs;
539
540       if (dict.TryGetValue (mdb.tag, out mdbs))
541         {
542           for (int i = 0; i < mdbs.Count; i++)
543             if (mdbs[i].ListIndex == mdb.ListIndex)
544               {
545                 mdbs[i].DBStatus = MDBStatus.INVALID;
546                 mdbs[i] = mdb;
547                 return;
548               }
549           mdbs.Add (mdb);
550         }
551       else
552         {
553           mdbs = new List<MDatabase> (1);
554           mdbs.Add (mdb);
555           dict.Add (mdb.tag, mdbs);
556         }
557     }
558
559     private static void register (int list_idx, int dir_idx,
560                                   Tag tag, MDatabaseInfo info)
561     {
562       Dictionary<MDatabase.Tag, List<MDatabase>> dict
563         = tag.HasWildcard ? wdict : ndict;
564       List<MDatabase> mdbs;
565       MDatabase mdb;
566
567       if (dict.TryGetValue (tag, out mdbs))
568         for (int i = 0; i < mdbs.Count; i++)
569           {
570             mdb = mdbs[i];
571             if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx)
572               {
573                 mdb.DirIndex = dir_idx;
574                 if (dict == wdict)
575                   {
576                     mdb.DBType = MDBType.WILDCARD;
577                     mdb.DBStatus = MDBStatus.NOT_READY;
578                   }
579                 else if (dir_idx == -1)
580                   {
581                     mdb.DBType = MDBType.AUTO;
582                     mdb.DBStatus = MDBStatus.NOT_READY;
583                   }
584                 else
585                   {
586                     mdb.DBType = MDBType.MULTIPLE;
587                     mdb.DBStatus = MDBStatus.READY;
588                   }
589                 mdb.Info = info;
590                 if (mdb.DBStatus == MDBStatus.INVALID)
591                   M17n.DebugPrint ("registering: {0}\n", mdb);
592                 else
593                   M17n.DebugPrint ("updating: {0}\n", mdb);
594                 return;
595               }
596           }
597       else
598         {
599           mdbs = new List<MDatabase> (1);
600           dict.Add (tag, mdbs);
601         }
602       mdb = new MDatabase (list_idx, dir_idx, tag, info);
603       M17n.DebugPrint ("registering: {0}\n", mdb);
604       mdbs.Add (mdb);
605     }
606
607     public MDatabase (Tag tag, Loader loader, object extra_info)
608     {
609       this.tag = tag;
610       this.loader = loader;
611       DBType = MDBType.UNKNOWN;
612       DBStatus = MDBStatus.READY;
613       DirIndex = 0;
614       ListIndex = 0;
615       ExtraInfo = extra_info;
616       register (this);
617     }
618
619     public MDatabase (Tag tag, string filename)
620     {
621       this.tag = tag;
622       DBType = MDBType.EXPLICIT;
623       if (Path.IsPathRooted (filename))
624         {
625           DirIndex = 0;
626           if (File.Exists (filename))
627             DBStatus = MDBStatus.READY;
628           else
629             DBStatus = MDBStatus.INVALID;
630         }
631       else
632         {
633           DirIndex = -1;
634           DBStatus = MDBStatus.NOT_READY;
635         }
636       ListIndex = 0;
637       Info = new MDatabaseInfo ();
638       Info.Filename = filename;
639       register (this);
640     }
641
642     private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info)
643     {
644       this.tag = tag;
645       this.Info = info;
646       DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO;
647       if (tag[0] == Mchar_table || tag[0] == Mcharset)
648         Info.Format = tag[0];
649       ListIndex = list_idx;
650       DirIndex = dir_idx;
651       if (Path.IsPathRooted (Info.Filename))
652         DBStatus = MDBStatus.READY;
653       else
654         DBStatus = MDBStatus.NOT_READY;
655       if (Info.Format == Mchar_table)
656         MCharProp.Define (tag[2], this);
657     }
658
659     public override String ToString () {
660       string str = ("#<MDataBase (" + ListIndex + "," + DirIndex + ") "
661                     + tag + " " + DBType + " " + DBStatus);
662
663       if (DBType != MDBType.EXPLICIT && DBType != MDBType.UNKNOWN)
664         str += " " + Info;
665       return str + ">";
666     }
667
668     // Update (or disable) databases defined by "mdb.dir" in
669     // DBDirs[list_idx].
670     private static void update_list (int list_idx)
671     {
672       M17n.DebugPrint ("Updating list: {0}\n", list_idx);
673       // At first disable all target databases.
674       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
675         foreach (MDatabase mdb in kv.Value)
676           if (mdb.ListIndex == list_idx)
677             {
678               M17n.DebugPrint ("deleting: {0}\n", mdb);
679               mdb.DBStatus = MDBStatus.INVALID;
680               break;
681             }
682       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
683         foreach (MDatabase mdb in kv.Value)
684           if (mdb.ListIndex == list_idx)
685             {
686               M17n.DebugPrint ("deleting: {0}\n", mdb);
687               mdb.DBStatus = MDBStatus.INVALID;
688               break;
689             }
690
691       FileInfo dblist = DBDirs[list_idx].ListInfo;
692       if (dblist == null)
693         return;
694
695       MPlist plist = null;
696       using (FileStream stream = File.OpenRead (dblist.FullName))
697         {
698           plist = new MPlist (stream);
699         }
700       if (plist == null)
701         return;
702       foreach (MPlist pl in plist)
703         if (pl.IsPlist)
704           {
705             try
706               {
707                 MPlist p = pl.Plist;
708                 Tag tag = new Tag (ref p);
709                 MDatabaseInfo info = new MDatabaseInfo (p);
710                 int dir_idx = Path.IsPathRooted (info.Filename) ? 0 : -1;
711                 register (list_idx, dir_idx, tag, info);
712               }
713             catch (Exception e)
714               {
715                 Console.WriteLine (e.Message + ": " + pl.Plist);
716               }
717           }
718     }
719
720     // Update (or disable) databases in DBDirs[dir_idx].
721     private static void update_dir (int dir_idx)
722     {
723       M17n.DebugPrint ("Updating dir: {0}\n", dir_idx);
724       // Reset all databases in DBDirs[dir_idx].
725       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
726         foreach (MDatabase mdb in kv.Value)
727           if (mdb.DirIndex >= dir_idx)
728             {
729               M17n.DebugPrint ("disabling: {0}\n", mdb);
730               mdb.DBStatus = MDBStatus.NOT_READY;
731               mdb.DirIndex = -1;
732             }
733       // Re-expand all WILDCARD databases in DBDirs[dir_idx].
734       if (DBDirs[dir_idx].DirInfo != null)
735         foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
736           foreach (MDatabase mdb in kv.Value)
737             if (mdb.DBStatus == MDBStatus.READY)
738               {
739                 M17n.DebugPrint ("re-expanding: {0}\n", mdb);
740                 register_files (DBDirs[dir_idx].Dirname, dir_idx, mdb);
741               }
742     }
743
744     private static bool parse_plist_header (FileInfo fileinfo, MDatabase mdb,
745                                             out Tag tag, out MDatabaseInfo info)
746     {
747       MPlist plist = null;
748
749       tag = new Tag (MSymbol.nil);
750       info = null;
751       using (FileStream stream = fileinfo.OpenRead ())
752         {
753           try { plist = new MPlist (stream, 1); } catch { }
754         }
755       if (plist == null || ! plist.IsPlist)
756         return false;
757       plist = plist.Plist;
758       tag = new Tag (ref plist);
759       if (tag.HasWildcard || ! tag.Match (mdb.tag))
760         return false;
761       info = new MDatabaseInfo (plist);
762       return true;
763     }
764
765     private static bool parse_xml_header (FileInfo fileinfo, MDatabase mdb,
766                                           out Tag tag, out MDatabaseInfo info)
767     {
768       tag = new Tag (MSymbol.nil);
769       info = null;
770       using (FileStream stream = fileinfo.OpenRead ())
771         {
772           try {
773             MPlist plist = new MPlist ();
774             XmlTextReader reader = new XmlTextReader (stream);
775             
776             reader.WhitespaceHandling = WhitespaceHandling.None;
777             do {
778               reader.Read ();
779             } while (reader.NodeType != XmlNodeType.Element);
780             plist.Add (MSymbol.symbol, (MSymbol) reader.Name);
781             reader.Read ();
782             if (reader.NodeType == XmlNodeType.Element && reader.Name == "tags")
783               {
784                 reader.Read ();
785                 while (reader.NodeType == XmlNodeType.Element)
786                   {
787                     reader.Read ();
788                     plist.Add (MSymbol.symbol, (MSymbol) reader.Value);
789                     reader.Read ();
790                     reader.Read ();
791                   }
792                 tag = new Tag (ref plist);
793                 if (tag.HasWildcard || ! tag.Match (mdb.tag))
794                   return false;
795                 info = new MDatabaseInfo (plist);
796                 return true;
797               }
798           } catch (Exception e) {
799             Console.WriteLine ("error {0}", e);
800           }
801         }
802       return false;
803     }
804
805     private static void register_files (string dir, int dir_idx, MDatabase mdb)
806     {
807       int list_idx = mdb.ListIndex;
808       List<FileInfo> files = new List<FileInfo> ();
809       MGlob.FileList (ref files, dir, mdb.Info.Filename);
810       foreach (FileInfo fileinfo in files)
811         {
812           Tag tag;
813           MDatabaseInfo info;
814
815           if (mdb.Info.Format == MSymbol.plist
816               ? parse_plist_header (fileinfo, mdb, out tag, out info)
817               : parse_xml_header (fileinfo, mdb, out tag, out info))
818             {
819               info.Merge (mdb.Info);
820               if (Path.IsPathRooted (mdb.Info.Filename))
821                 info.Filename = fileinfo.FullName;
822               else
823                 info.Filename = fileinfo.Name;
824               register (list_idx, dir_idx, tag, info);
825             }
826         }
827     }
828
829     private void expand_wildcard ()
830     {
831       M17n.DebugPrint ("expanding: {0}\n", this);
832
833       if (DirIndex == 0)
834         register_files (null, DirIndex, this);
835       else
836         for (int i = 1; i < 4; i++)
837           if (DBDirs[i].DirInfo != null)
838             register_files (DBDirs[i].DirInfo.FullName, i, this);
839       DBStatus = MDBStatus.READY;
840     }
841
842     private static void maybe_expand_wildcard (Tag tag)
843     {
844       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
845         {
846           M17n.DebugPrint ("expand check: {0}\n", kv.Key);
847           if (kv.Key.Match (tag))
848             {
849               foreach (MDatabase mdb in kv.Value)
850                 {
851                   if (mdb.DBStatus == MDBStatus.NOT_READY)
852                     mdb.expand_wildcard ();
853                 }
854             }
855         }
856     }
857
858     // Update the status.  Return true iff the database file is
859     // readable but changed.
860
861     private bool update_status ()
862     {
863       if (DBType == MDBType.UNKNOWN)
864         return true;
865       update_all (false);
866       if (DBStatus == MDBStatus.INVALID)
867         return false;
868       if (DBStatus != MDBStatus.NOT_READY)
869         {
870           try {
871             FileInfo.Refresh ();
872           } catch {
873             DBStatus = MDBStatus.INVALID;
874             return false;
875           }
876           if (LastLoaded >= FileInfo.LastWriteTime)
877             return false;
878           DBStatus = MDBStatus.READY;
879           return true;
880         }         
881       for (int i = 1; i < 4; i++)
882         if (DBDirs[i] != null && DBDirs[i].Dirname != null)
883           {
884             string filename = Path.Combine (DBDirs[i].Dirname, Info.Filename);
885             if (File.Exists (filename))
886               {
887                 FileInfo = new FileInfo (filename);
888                 DirIndex = i;
889                 DBStatus = MDBStatus.READY;
890                 return true;
891               }
892           }
893       return false;
894     }
895
896     public static MDatabase Find (Tag tag)
897     {
898       List<MDatabase> mdbs;
899       MDatabase mdb = null;
900
901       if (tag.HasWildcard)
902         throw new ArgumentException ("Wildcard not allowed: " + tag);
903
904       if (ndict.TryGetValue (tag, out mdbs))
905         {
906           mdbs.Sort ();
907           for (int i = 0; i < mdbs.Count; i++)
908             {
909               mdb = mdbs[i];
910               if (mdb.ListIndex == 0)
911                 return mdb;
912               if (mdb.DBStatus == MDBStatus.READY)
913                 break;
914             }
915         }
916       if (! update_all (false) && mdb != null)
917         return mdb;
918       maybe_expand_wildcard (tag);
919       if (! ndict.TryGetValue (tag, out mdbs))
920         return null;
921       for (int i = 0; i < mdbs.Count; i++)
922         {
923           mdb = mdbs[i];
924           mdb.update_status ();
925           if (mdb.DBStatus == MDBStatus.READY)
926             return mdb;
927         }
928       return null;
929     }
930
931     public static List<MDatabase> List (Tag tag)
932     {
933       List<MDatabase> list = new List<MDatabase> ();
934
935       update_all (false);
936       maybe_expand_wildcard (tag);
937
938       if (tag.HasWildcard)
939         {
940           foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
941             if (kv.Key.Match (tag))
942               foreach (MDatabase mdb in kv.Value)
943                 {
944                   mdb.update_status ();
945                   if (mdb.DBStatus == MDBStatus.READY)
946                     {
947                       list.Add (mdb);
948                       break;
949                     }
950                 }
951         }
952       else
953         {
954           List<MDatabase> mdbs;
955           if (ndict.TryGetValue (tag, out mdbs))
956             foreach (MDatabase mdb in mdbs)
957               {
958                 mdb.update_status ();
959                 if (mdb.DBStatus == MDBStatus.READY)
960                   {
961                     list.Add (mdb);
962                     break;
963                   }
964               }
965         }
966       return list;
967     }
968
969     private FileStream get_stream ()
970     {
971       if (loader != null
972           || (Info.Format != MSymbol.plist && Info.Format != Mxml))
973         {
974           LastLoadStatus = LoadStatus.InvalidLoadMethod;
975           return null;
976         }
977       if (DBStatus != MDBStatus.READY)
978         {
979           LastLoadStatus = LoadStatus.NotAvailable;
980           return null;
981         }
982
983       FileStream stream = null;
984       try {
985         stream = FileInfo.OpenRead ();
986       } catch {
987         LastLoadStatus = LoadStatus.NotReadable;          
988       }
989       return stream;
990     }
991
992     public object Load ()
993     {
994       if (loader != null)
995         return loader (tag, ExtraInfo);
996       if (Info.Format == Mxml)
997         {
998           XmlDocument doc = new XmlDocument ();
999           try {
1000             XmlReader reader = XmlReader.Create (FileInfo.FullName);
1001             doc.Load (reader);
1002             LastLoaded = DateTime.Now;      
1003           } catch (Exception e) {
1004             Console.WriteLine (e);
1005             LastLoadStatus = LoadStatus.InvalidContents;
1006           }
1007           return doc;
1008         }
1009
1010       FileStream stream = get_stream ();
1011       if (stream == null)
1012         return null;
1013       MPlist plist = null;
1014       try {
1015         plist = new MPlist (stream);
1016         LastLoaded = DateTime.Now;
1017       } catch {
1018         LastLoadStatus = LoadStatus.InvalidContents;
1019       } finally {
1020         stream.Dispose ();
1021       }
1022       return plist;
1023     }
1024
1025     public object Load (MSymbol key, MSymbol stop)
1026     {
1027       FileStream stream = get_stream ();
1028
1029       if (stream == null)
1030         return null;
1031       if (Info.Format == Mxml)
1032         {
1033           XmlDocument doc = new XmlDocument ();
1034           XmlTextReader reader = new XmlTextReader (stream);
1035
1036           reader.WhitespaceHandling = WhitespaceHandling.None;
1037           try {
1038             reader.Read ();
1039             while (reader.NodeType != XmlNodeType.Element)
1040               reader.Read ();
1041             doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
1042             reader.Read ();
1043             XmlNode node = doc.DocumentElement;
1044             while (reader.NodeType == XmlNodeType.Element
1045                    ? reader.Name != stop.Name
1046                    : reader.NodeType != XmlNodeType.EndElement)
1047               if (reader.NodeType == XmlNodeType.Element
1048                   && reader.Name == key.Name)
1049                 node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
1050                                                         node);
1051           } finally {
1052             reader.Close ();
1053             stream.Dispose ();
1054           }
1055           return doc;
1056         }
1057
1058       MPlist plist = null;
1059       try {
1060         plist = new MPlist (stream, key, stop);
1061         LastLoaded = DateTime.Now;
1062       } catch {
1063         LastLoadStatus = LoadStatus.InvalidContents;
1064       } finally {
1065         stream.Dispose ();
1066       }
1067       return plist;
1068     }
1069
1070     public object Load (MSymbol stop)
1071     {
1072       FileStream stream = get_stream ();
1073
1074       if (stream == null)
1075         return null;
1076       if (Info.Format == Mxml)
1077         {
1078           XmlDocument doc = new XmlDocument ();
1079           XmlTextReader reader = new XmlTextReader (stream);
1080
1081           reader.WhitespaceHandling = WhitespaceHandling.None;
1082           try {
1083             reader.Read ();
1084             while (reader.NodeType != XmlNodeType.Element)
1085               reader.Read ();
1086             doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
1087             reader.Read ();
1088             XmlNode node = null;
1089             while (reader.NodeType == XmlNodeType.Element
1090                    ? reader.Name != stop.Name
1091                    : reader.NodeType != XmlNodeType.EndElement)
1092               if (reader.NodeType == XmlNodeType.Element)
1093                   node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
1094                                                           node);
1095           } catch (Exception e) {
1096             Console.WriteLine (e);
1097           } finally {
1098             reader.Close ();
1099             stream.Dispose ();
1100           }
1101           return doc;
1102         }
1103
1104       MPlist plist = null;
1105       try {
1106         plist = new MPlist (stream, stop);
1107         LastLoaded = DateTime.Now;
1108       } catch (Exception e) {
1109         Console.WriteLine (e);
1110         LastLoadStatus = LoadStatus.InvalidContents;
1111       } finally {
1112         stream.Dispose ();
1113       }
1114       return plist;
1115     }
1116
1117     public XmlNode Load (string id, params string[] nodes)
1118     {
1119       FileStream stream = get_stream ();
1120       if (stream == null)
1121         return null;
1122       if (Info.Format != Mxml)
1123         throw new Exception ("Not an XML format");
1124
1125       XmlDocument doc = new XmlDocument ();
1126       XmlTextReader reader = new XmlTextReader (stream);
1127       int len = nodes.Length;
1128
1129       reader.WhitespaceHandling = WhitespaceHandling.None;
1130       do {
1131         reader.Read ();
1132       } while (reader.NodeType != XmlNodeType.Element);
1133
1134       if (reader.Name != nodes[0])
1135         return null;
1136
1137       string ns = reader.GetAttribute ("xmlns");
1138       XmlNode top = doc.CreateNode (XmlNodeType.Element, nodes[0], ns);
1139       XmlNode node = top;
1140
1141       try {
1142         int i;
1143
1144         for (i = 1; i + 1 < len; i++)
1145           {
1146             if (! reader.ReadToDescendant (nodes[i]))
1147               return null;
1148             node = node.InsertAfter (doc.CreateNode (XmlNodeType.Element,
1149                                                      nodes[i], ns), null);
1150           }
1151         if (! reader.ReadToDescendant (nodes[i]))
1152           return null;
1153         XmlNode ref_node = null;
1154         while (reader.NodeType != XmlNodeType.EndElement)
1155           {
1156             if (reader.NodeType == XmlNodeType.Element)
1157               {
1158                 if (reader.Name == nodes[i]
1159                     && (id == null || id == reader.GetAttribute ("id")))
1160                   ref_node = node.InsertAfter (doc.ReadNode (reader), ref_node);
1161                 else
1162                   reader.Skip ();
1163               }
1164             else
1165               reader.Read ();
1166           }
1167               
1168       } catch (Exception e) {
1169         Console.WriteLine (e);
1170       } finally {
1171         reader.Close ();
1172         stream.Dispose ();
1173       }
1174       return top;
1175     }
1176
1177     /// <summary>Return a list of currently available database
1178     /// directory names</summary>.
1179     public static string[] DirectoryList ()
1180     {
1181       List<string> dirs = new List<string> ();
1182
1183       for (int i = 1; i < 4; i++)
1184         if (DBDirs[i].Dirname != null)
1185           dirs.Add (DBDirs[i].Dirname);
1186       return dirs.ToArray ();
1187     }
1188
1189     public MSymbol Format { get { return Info.Format; } }
1190
1191     public static bool Changed (DateTime time)
1192     {
1193       update_all (false);
1194       return (time < LastUpdateTime);
1195     }
1196
1197     public bool NeedReload ()
1198     {
1199       return update_status ();
1200     }  
1201
1202     // For IComparable<MDatabase>
1203     public int CompareTo (MDatabase other)
1204     {
1205       return (ListIndex == other.ListIndex
1206               ? DirIndex - other.DirIndex
1207               : ListIndex - other.ListIndex);
1208     }
1209   }
1210 }