*** 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 M17N;
6 using M17N.Core;
7
8 namespace M17N.Core
9 {
10   internal class MGlob
11   {
12     static readonly char sep = Path.DirectorySeparatorChar;
13
14     public static void FileList (ref List<FileInfo> files,
15                                  string dir, string pattern)
16     {
17       int len = pattern.Length;
18
19       if (Path.IsPathRooted (pattern))
20         {
21           int headsep = 0;
22           int i;
23
24           for (i = 1; i < len && (pattern[i] != '*' && pattern[i] != '?'); i++)
25             if (pattern[i] == sep)
26               headsep = i;
27           if (i == len)
28             {
29               if (File.Exists (pattern))
30                 files.Add (new FileInfo (pattern));
31               return;
32             }
33           dir = pattern.Substring (0, headsep);
34           pattern = pattern.Substring (headsep + 1);
35         }
36       else
37         {
38           if (dir == null)
39             dir = Directory.GetCurrentDirectory ();
40         }
41       if (Directory.Exists (dir))
42         list (ref files, new DirectoryInfo (dir), pattern);
43     }
44
45     private static void list (ref List<FileInfo> files,
46                               DirectoryInfo dirinfo, string pattern)
47     {
48       int len = pattern.Length;
49       int i;
50     
51       for (i = 0; i < len && pattern[i] != sep; i++);
52       try {
53         if (i == len)
54           {
55             FileInfo[] listing = dirinfo.GetFiles (pattern);
56             foreach (FileInfo elt in listing)
57               files.Add (elt);
58           }
59         else
60           {
61             string tail = pattern.Substring (i + 1);
62             pattern = pattern.Substring (0, i);
63             DirectoryInfo[] listing = dirinfo.GetDirectories (pattern);
64             foreach (DirectoryInfo elt in listing)
65               list (ref files, elt, tail);
66           }
67       } catch {
68       }
69     }
70   }
71
72   internal class MDatabaseDir
73   {
74     private const string ListFileName = "mdb.dir";
75
76     public string Dirname;
77     public DirectoryInfo DirInfo;
78     public DateTime DirChangeTime;
79     public FileInfo ListInfo;
80     public DateTime ListChangeTime;
81
82     public MDatabaseDir (string dirname)
83     {
84       Dirname = dirname;
85       DirChangeTime = ListChangeTime = DateTime.Now;
86     }
87
88     public void Refresh ()
89     {
90       if (DirInfo != null)
91         {
92           if (Dirname != null && Directory.Exists (Dirname))
93             {
94               DirInfo.Refresh ();
95               if (DirChangeTime < DirInfo.LastWriteTime)
96                 DirChangeTime = DirInfo.LastWriteTime;
97             }
98           else
99             {
100               DirInfo = null;
101               DirChangeTime = DateTime.Now;
102             }
103         }
104       else
105         {
106           if (Dirname != null && Directory.Exists (Dirname))
107             {
108               DirInfo = new DirectoryInfo (Dirname);
109               DirChangeTime = DateTime.Now;
110             }
111         }
112       if (DirInfo == null)
113         {
114           if (ListInfo != null)
115             {
116               ListInfo = null;
117               ListChangeTime = DateTime.Now;
118             }
119         }
120       else
121         {
122           if (ListInfo != null)
123             {
124               ListInfo.Refresh ();
125               if (ListChangeTime < ListInfo.LastWriteTime)
126                 ListChangeTime = ListInfo.LastWriteTime;
127             }
128           else
129             {
130               try {
131                 ListInfo = DirInfo.GetFiles (ListFileName)[0];
132                 ListChangeTime = DateTime.Now;
133               } catch {
134               }
135             }
136         }
137     }
138   }
139
140   public partial class MDatabase : IComparable<MDatabase>
141   {
142     /// Identifier of a MDatabase.
143     public struct Tag : IEquatable<Tag>
144     {
145       private MSymbol[] Tags;
146
147       public Tag (MSymbol tag0)
148         {
149           Tags = new MSymbol[4];
150           Tags[0] = tag0; Tags[1] = Tags[2] = Tags[3] = MSymbol.nil;
151         }
152
153       public Tag (MSymbol tag0, MSymbol tag1)
154         {
155           Tags = new MSymbol[4];
156           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = Tags[3] = MSymbol.nil;
157         }
158
159       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2)
160         {
161           Tags = new MSymbol[4];
162           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = MSymbol.nil;
163         }
164
165       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
166         {
167           Tags = new MSymbol[4];
168           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = tag3;
169         }
170
171       public Tag (ref MPlist plist)
172         {
173           Tags = new MSymbol[4];
174
175           for (int i = 0; i < 4; i++)
176             {
177               if (plist.IsSymbol)
178                 {
179                   Tags[i] = plist.Symbol;
180                   plist = plist.Next;
181                 }
182               else
183                 Tags[i] = MSymbol.nil;
184             }
185         }
186
187       public bool Equals (Tag tag)
188       {
189         for (int i = 0; i < 4; i++)
190           if (tag[i] != Tags[i])
191             return false;
192         return true;
193       }
194
195       public override int GetHashCode ()
196       {
197         return (Tags[0].GetHashCode () ^ Tags[1].GetHashCode ()
198                 ^ Tags[2].GetHashCode () ^ Tags[3].GetHashCode ());
199       }
200
201       public override string ToString ()
202       {
203         return ("<"
204                 + Tags[0] + "," + Tags[1] + "," + Tags[2] + "," + Tags[3]
205                 + ">");
206       }
207
208       public MSymbol this[int i]
209       {
210         set { Tags[i] = value; }
211         get { return Tags[i]; }
212       }
213
214       public bool Match (Tag tag)
215       {
216         for (int i = 0; i < 4; i++)
217           {
218             if (tag[i] == Mwildcard || Tags[i] == Mwildcard)
219               return true;
220             if (tag[i] != Tags[i])
221               return false;
222           }
223         return true;
224       }
225
226       public bool HasWildcard {
227         get {
228           for (int i = 0; i < 4; i++)
229             if (Tags[i] == Mwildcard)
230               return true;
231           return false;
232         }
233       }
234     }
235
236     public delegate object Loader (Tag tag, object extra_info);
237
238     internal class MDatabaseInfo
239     {
240       // These come from the declartion plist of the database.
241       internal string Description;
242       internal string Filename;
243       internal FileInfo Validater;
244       internal int Version;
245       internal MSymbol Format;
246       internal MSymbol Schema;
247       internal string SchemaFile;
248       internal MPlist Props;
249
250       public MDatabaseInfo ()
251       {
252         Format = Schema = MSymbol.nil;
253       }
254
255       private static int parse_version (MPlist plist)
256       {
257         string[] str;
258         int major, minor, release;
259
260         if (! plist.IsMText)
261           return 0xFFFFFF;
262         str = plist.Text.ToString ().Split ('.');
263         if (str.Length != 3)
264           return 0xFFFFFF;
265         try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; }
266         try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; }
267         try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; }
268         return ((major << 16) | (minor << 8) | release);
269       }
270
271       public MDatabaseInfo (MPlist plist)
272       {
273         Format = MSymbol.plist;
274         if (plist.IsMText)
275           {
276             Filename = plist.Text.ToString ();
277             plist = plist.Next;
278           }
279         else if (plist.IsPlist)
280           {
281             MPlist p = plist.Plist;
282
283             if (p.IsMText)
284               Filename = p.Text.ToString ();
285             p = p.Next;
286             if (! p.IsEmpty)
287               {
288                 if (p.IsSymbol)
289                   Format = p.Symbol;
290                 p = p.Next;
291                 if (! p.IsEmpty)
292                   {
293                     if (p.IsSymbol)
294                       Schema = p.Symbol;
295                     p = p.Next;
296                     if (p.IsMText)
297                       SchemaFile = p.Text.ToString ();
298                   }                 
299               }
300             plist = plist.Next;
301           }
302
303         Version = 0;
304         Props = new MPlist ();
305         foreach (MPlist pl in plist)
306           {
307             if (pl.IsPlist)
308               {
309                 MPlist p = pl.Plist;
310               
311                 if (p.IsSymbol && p.Symbol == Mversion)
312                   Version = parse_version (p.Next);
313                 else
314                   Props.Put (pl.Key, pl.Val);
315               }
316             else if (pl.IsSymbol)
317               {
318                 MPlist p = new MPlist ();
319                 p.Add (MSymbol.symbol, pl.Symbol);
320                 p.Add (MSymbol.symbol, MSymbol.t);
321                 Props.Put (MSymbol.plist, p);
322               }
323           }
324       }
325
326       public void Merge (MDatabaseInfo src)
327       {
328         if (Validater == null)
329           Validater = src.Validater;
330         if (Version == 0)
331           Version = src.Version;
332         if (Format == MSymbol.nil)
333           Format = src.Format;
334         if (Schema == MSymbol.nil)
335           Schema = src.Schema;
336         if (SchemaFile == null)
337           SchemaFile = src.SchemaFile;
338         foreach (MPlist p in src.Props)
339           if (Props.Assq (p.Plist.Symbol) == null)
340             Props.Push (p.Key, p.Val);
341       }
342
343       public override string ToString ()
344       {
345         string str = ("#<Info " + Format + " \"" + Filename + "\"");
346         if (Schema != MSymbol.nil)
347           str += " " + Schema;
348         return str + ">";
349       }
350     }
351
352     // Dictionaries for normal databases.
353     private static Dictionary<MDatabase.Tag, List<MDatabase>> ndict
354       = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
355
356     // Dictionaries for databases of DBType WILDCARD
357     private static Dictionary<MDatabase.Tag, List<MDatabase>> wdict
358       = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
359
360     private static MDatabaseDir[] DBDirs = new MDatabaseDir[4];
361
362     private static DateTime LastUpdateTime = new DateTime (0);
363
364     private static readonly MSymbol Mversion = MSymbol.Of ("version");
365     private static readonly MSymbol Mwildcard = MSymbol.Of ("*");
366     private static readonly MSymbol Mchar_table = MSymbol.Of ("char-table");
367     private static readonly MSymbol Mcharset = MSymbol.Of ("charset");
368
369     /// Type of database
370     private enum MDBType
371       {
372         /// The database was defined automatically from one of mdb.dir
373         /// files with no wildcard tag.
374         AUTO,
375         /// The database was defined automatically from one of mdb.dir
376         /// files with a wildcard tag to define multiple databases
377         /// of the same kind.
378         MULTIPLE,
379         /// The database was defined explicitely by MDatabase.Define
380         /// to use the normal loader.
381         EXPLICIT,
382         /// The database was defined explicitely by MDatabase.Define
383         /// to use a special loader.
384         UNKNOWN,
385         /// The database is for defining multiple databases of the
386         /// same kind with a wildcard tag.
387         WILDCARD,
388       };
389
390     /// Status of database
391     private enum MDBStatus
392       {
393         // The database file has not yet been decided, or is not yet
394         // expanded if DBType is WILDCARD.
395         NOT_READY,
396         // The database file was decided, or has been expanded if
397         // DBType is WILDCARD.
398         READY,
399         // The database is disabled.  It means that the database file
400         // is not readable, the version is not supported by the
401         // current system, or the validation was failed.
402         DISABLED,
403         // The database is deleted by the modificaiton of mdb.dir, or
404         // is overwritten by a new explicit definition..
405         INVALID,
406       };
407
408     public Tag tag;
409     private Loader loader;
410     private object ExtraInfo;
411     // Directory of the database file.
412     // -1:unknown, 0:absolute, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
413     private int DirIndex;
414     // Directory of the mdb.dir defining the database file.
415     // 0: EXPLICIT or UNKNOWN, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
416     private int ListIndex;
417     private MDBType DBType;
418     private MDBStatus DBStatus;
419     internal MDatabaseInfo Info;
420     // File in which the database contents is stored.
421     internal FileInfo FileInfo;
422     // When the database file is checked (or validated).
423     internal DateTime CheckedTime;
424
425     static MDatabase ()
426     {
427       string share_dir = (Environment.GetFolderPath
428                           (Environment.SpecialFolder.CommonApplicationData));
429       string usr_dir = (Environment.GetFolderPath
430                         (Environment.SpecialFolder.ApplicationData));
431
432       try {
433         string dir = Environment.GetEnvironmentVariable ("M17NDIR");
434         DBDirs[1] = new MDatabaseDir (dir);
435       } catch {
436         try {
437           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
438         } catch (ArgumentException) {
439           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
440         }
441       }
442       DBDirs[2] = new MDatabaseDir (null);
443       DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
444       update_all ();
445     }
446
447     public static string ApplicationDir
448     {
449       get { return (DBDirs[1].Dirname); }
450       set { DBDirs[2] = new MDatabaseDir (value); update_all (); }
451     }
452
453     private static bool update_all ()
454     {
455       bool updated = false;
456
457       for (int i = 1; i < 4; i++)
458         if (DBDirs[i].Dirname != null)
459           {
460             DBDirs[i].Refresh ();
461             if (LastUpdateTime < DBDirs[i].ListChangeTime)
462               {
463                 update_list (i);
464                 updated = true;
465               }
466             if (LastUpdateTime < DBDirs[i].DirChangeTime)
467               {
468                 update_dir (i);
469                 updated = true;
470               }
471           }
472       LastUpdateTime = DateTime.Now;
473       return updated;
474     }
475
476     public static void Dump ()
477     {
478       update_all ();
479       Console.WriteLine ("[DBDirs]");
480       for (int i = 1; i < 4; i++)
481         if (DBDirs[i].Dirname != null)
482           {
483             if (DBDirs[i].DirInfo != null)
484               {
485                 Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName);
486                 if (DBDirs[i].ListInfo != null)
487                   Console.WriteLine (" {0}", DBDirs[i].ListInfo);
488                 else
489                   Console.WriteLine (" .. no mdb.dir");
490               }
491             else
492               Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname);
493           }
494
495       Console.WriteLine ("[WDICT]");
496       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
497         foreach (MDatabase mdb in kv.Value)
498           Console.WriteLine (mdb);
499
500       Console.WriteLine ("[NDICT]");
501       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
502         foreach (MDatabase mdb in kv.Value)
503           Console.WriteLine (mdb);
504     }
505
506     private static void register (MDatabase mdb)
507     {
508       Dictionary<MDatabase.Tag, List<MDatabase>> dict
509         = mdb.DBType == MDBType.WILDCARD ? wdict : ndict;
510       List<MDatabase> mdbs;
511
512       if (dict.TryGetValue (mdb.tag, out mdbs))
513         {
514           for (int i = 0; i < mdbs.Count; i++)
515             if (mdbs[i].ListIndex == mdb.ListIndex)
516               {
517                 mdbs[i].DBStatus = MDBStatus.INVALID;
518                 mdbs[i] = mdb;
519                 return;
520               }
521           mdbs.Add (mdb);
522         }
523       else
524         {
525           mdbs = new List<MDatabase> (1);
526           mdbs.Add (mdb);
527           dict.Add (mdb.tag, mdbs);
528         }
529     }
530
531     private static void register (int list_idx, int dir_idx,
532                                   Tag tag, MDatabaseInfo info)
533     {
534       Dictionary<MDatabase.Tag, List<MDatabase>> dict
535         = tag.HasWildcard ? wdict : ndict;
536       List<MDatabase> mdbs;
537       MDatabase mdb;
538
539       if (dict.TryGetValue (tag, out mdbs))
540         for (int i = 0; i < mdbs.Count; i++)
541           {
542             mdb = mdbs[i];
543             if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx)
544               {
545                 if (mdb.DBStatus == MDBStatus.INVALID)
546                   M17n.DebugPrint ("registering: {0}\n", mdb);
547                 else
548                   M17n.DebugPrint ("updating: {0}\n", mdb);
549                 mdb.DirIndex = dir_idx;
550                 if (dict == wdict)
551                   {
552                     mdb.DBType = MDBType.WILDCARD;
553                     mdb.DBStatus = MDBStatus.NOT_READY;
554                   }
555                 else if (dir_idx == -1)
556                   {
557                     mdb.DBType = MDBType.AUTO;
558                     mdb.DBStatus = MDBStatus.NOT_READY;
559                   }
560                 else
561                   {
562                     mdb.DBType = MDBType.MULTIPLE;
563                     mdb.DBStatus = MDBStatus.READY;
564                   }
565                 mdb.Info = info;
566                 return;
567               }
568           }
569       else
570         {
571           mdbs = new List<MDatabase> (1);
572           dict.Add (tag, mdbs);
573         }
574       mdb = new MDatabase (list_idx, dir_idx, tag, info);
575       M17n.DebugPrint ("registering: {0}\n", mdb);
576       mdbs.Add (mdb);
577     }
578
579     public MDatabase (Tag tag, Loader loader, object extra_info)
580     {
581       this.tag = tag;
582       this.loader = loader;
583       DBType = MDBType.UNKNOWN;
584       DBStatus = MDBStatus.READY;
585       DirIndex = 0;
586       ListIndex = 0;
587       ExtraInfo = extra_info;
588       register (this);
589     }
590
591     public MDatabase (Tag tag, string filename)
592     {
593       this.tag = tag;
594       DBType = MDBType.EXPLICIT;
595       if (Path.IsPathRooted (filename))
596         {
597           DirIndex = 0;
598           if (File.Exists (filename))
599             DBStatus = MDBStatus.READY;
600           else
601             DBStatus = MDBStatus.INVALID;
602         }
603       else
604         {
605           DirIndex = -1;
606           DBStatus = MDBStatus.NOT_READY;
607         }
608       ListIndex = 0;
609       Info = new MDatabaseInfo ();
610       Info.Filename = filename;
611       register (this);
612     }
613
614     private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info)
615     {
616       this.tag = tag;
617       this.Info = info;
618       DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO;
619       if (tag[0] == Mchar_table || tag[0] == Mcharset)
620         Info.Format = tag[0];
621       ListIndex = list_idx;
622       DirIndex = dir_idx;
623       if (Path.IsPathRooted (Info.Filename))
624         DBStatus = MDBStatus.READY;
625       else
626         DBStatus = MDBStatus.NOT_READY;
627       if (Info.Format == Mchar_table)
628         MCharProp.Define (tag[2], this);
629     }
630
631     public override String ToString () {
632       string str = ("#<MDataBase (" + ListIndex + "," + DirIndex + ") "
633                     + tag + " " + DBType + " " + DBStatus);
634
635       if (DBType != MDBType.EXPLICIT && DBType != MDBType.UNKNOWN)
636         str += " " + Info;
637       return str + ">";
638     }
639
640     // Update (or disable) databases defined by "mdb.dir" in
641     // DBDirs[list_idx].
642     private static void update_list (int list_idx)
643     {
644       M17n.DebugPrint ("Updating list: {0}\n", list_idx);
645       // At first disable all target databases.
646       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
647         foreach (MDatabase mdb in kv.Value)
648           if (mdb.ListIndex == list_idx)
649             {
650               M17n.DebugPrint ("deleting: {0}\n", mdb);
651               mdb.DBStatus = MDBStatus.INVALID;
652               break;
653             }
654       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
655         foreach (MDatabase mdb in kv.Value)
656           if (mdb.ListIndex == list_idx)
657             {
658               M17n.DebugPrint ("deleting: {0}\n", mdb);
659               mdb.DBStatus = MDBStatus.INVALID;
660               break;
661             }
662
663       FileInfo dblist = DBDirs[list_idx].ListInfo;
664       if (dblist == null)
665         return;
666
667       MPlist plist = null;
668       using (FileStream stream = File.OpenRead (dblist.FullName))
669         {
670           plist = new MPlist (stream);
671         }
672       if (plist == null)
673         return;
674       foreach (MPlist pl in plist)
675         if (pl.IsPlist)
676           {
677             try
678               {
679                 MPlist p = pl.Plist;
680                 Tag tag = new Tag (ref p);
681                 MDatabaseInfo info = new MDatabaseInfo (p);
682                 int dir_idx = Path.IsPathRooted (info.Filename) ? 0 : -1;
683                 register (list_idx, dir_idx, tag, info);
684               }
685             catch (Exception e)
686               {
687                 Console.WriteLine (e.Message + ": " + pl.Plist);
688               }
689           }
690     }
691
692     // Update (or disable) databases in DBDirs[dir_idx].
693     private static void update_dir (int dir_idx)
694     {
695       M17n.DebugPrint ("Updating dir: {0}\n", dir_idx);
696       // Reset all databases in DBDirs[dir_idx].
697       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
698         foreach (MDatabase mdb in kv.Value)
699           if (mdb.DirIndex >= dir_idx)
700             {
701               M17n.DebugPrint ("disabling: {0}\n", mdb);
702               mdb.DBStatus = MDBStatus.NOT_READY;
703               mdb.DirIndex = -1;
704             }
705       // Re-expand all WILDCARD databases in DBDirs[dir_idx].
706       if (DBDirs[dir_idx].DirInfo != null)
707         foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
708           foreach (MDatabase mdb in kv.Value)
709             if (mdb.DBStatus == MDBStatus.READY)
710               {
711                 M17n.DebugPrint ("re-expanding: {0}\n", mdb);
712                 register_files (DBDirs[dir_idx].Dirname, mdb.ListIndex,
713                                 dir_idx, mdb.Info);
714               }
715     }
716
717     private static void register_files (string dir, int list_idx, int dir_idx,
718                                         MDatabaseInfo base_info)
719     {
720       List<FileInfo> files = new List<FileInfo> ();
721       MGlob.FileList (ref files, dir, base_info.Filename);
722       foreach (FileInfo fileinfo in files)
723         {
724           MPlist plist = null;
725           using (FileStream stream = fileinfo.OpenRead ())
726             {
727               plist = new MPlist (stream, 1);
728             }
729           if (plist != null && plist.IsPlist)
730             {
731               plist = plist.Plist;
732               Tag tag = new Tag (ref plist);
733
734               if (! tag.HasWildcard && tag.Match (tag))
735                 {
736                   MDatabaseInfo info = new MDatabaseInfo (plist);
737                   info.Merge (base_info);
738                   if (Path.IsPathRooted (base_info.Filename))
739                     info.Filename = fileinfo.FullName;
740                   else
741                     info.Filename = fileinfo.Name;
742                   register (list_idx, dir_idx, tag, info);
743                 }
744             }
745         }
746     }
747
748     private void expand_wildcard ()
749     {
750       M17n.DebugPrint ("expanding: {0}\n", this);
751
752       if (DirIndex == 0)
753         register_files (null, ListIndex, DirIndex, Info);
754       else
755         for (int i = 1; i < 4; i++)
756           if (DBDirs[i].DirInfo != null)
757             register_files (DBDirs[i].DirInfo.FullName, ListIndex, i, Info);
758       DBStatus = MDBStatus.READY;
759     }
760
761     private static void maybe_expand_wildcard (Tag tag)
762     {
763       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
764         {
765           M17n.DebugPrint ("expand check: {0}\n", kv.Key);
766           if (kv.Key.Match (tag))
767             {
768               foreach (MDatabase mdb in kv.Value)
769                 {
770                   if (mdb.DBStatus == MDBStatus.NOT_READY)
771                     mdb.expand_wildcard ();
772                 }
773             }
774         }
775     }
776
777     private bool update_status ()
778     {
779       if (DBType == MDBType.UNKNOWN)
780         return true;
781       if (DBStatus == MDBStatus.INVALID)
782         return false;
783       if (DBStatus == MDBStatus.READY)
784         {
785           if (DirIndex > 0
786               || File.Exists (FileInfo.FullName))
787             return true;
788           DBStatus = MDBStatus.INVALID;
789           return false;
790         }
791       for (int i = 1; i < 4; i++)
792         if (DBDirs[i] != null && DBDirs[i].Dirname != null)
793           {
794             string filename = Path.Combine (DBDirs[i].Dirname, Info.Filename);
795             if (File.Exists (filename))
796               {
797                 FileInfo = new FileInfo (filename);
798                 DirIndex = i;
799                 return true;
800             }
801           }
802       return false;
803     }
804
805     public static MDatabase Find (Tag tag)
806     {
807       List<MDatabase> mdbs;
808       MDatabase mdb = null;
809
810       if (tag.HasWildcard)
811         throw new ArgumentException ("Wildcard not allowed: " + tag);
812
813       if (ndict.TryGetValue (tag, out mdbs))
814         {
815           mdbs.Sort ();
816           for (int i = 0; i < mdbs.Count; i++)
817             {
818               mdb = mdbs[i];
819               if (mdb.ListIndex == 0)
820                 return mdb;
821               if (mdb.DBStatus == MDBStatus.READY)
822                 break;
823             }
824         }
825       if (! update_all () && mdb != null)
826         return mdb;
827       maybe_expand_wildcard (tag);
828       if (! ndict.TryGetValue (tag, out mdbs))
829         return null;
830       for (int i = 0; i < mdbs.Count; i++)
831         if ((mdb = mdbs[i]).update_status ())
832           return mdb;
833       return null;
834     }
835
836     public static List<MDatabase> List (Tag tag)
837     {
838       List<MDatabase> list = new List<MDatabase> ();
839
840       update_all ();
841       maybe_expand_wildcard (tag);
842
843       if (tag.HasWildcard)
844         {
845           foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
846             if (kv.Key.Match (tag))
847               foreach (MDatabase mdb in kv.Value)
848                 if (mdb.update_status ())
849                   {
850                     list.Add (mdb);
851                     break;
852                   }
853         }
854       else
855         {
856           List<MDatabase> mdbs;
857           if (ndict.TryGetValue (tag, out mdbs))
858             foreach (MDatabase mdb in mdbs)
859               if (mdb.update_status ())
860                 {
861                   list.Add (mdb);
862                   break;
863                 }
864         }
865       return list;
866     }
867
868     public object Load ()
869     {
870       if (loader != null)
871         return loader (tag, ExtraInfo);
872       if (Info.Format == Mchar_table)
873         throw new Exception ("Use Load (MCharTable) to load this database");
874       if (Info.Format == Mcharset)
875         throw new Exception ("Use Load (MCharset) to load this database");
876       if (! update_status ())
877         throw new Exception ("Database invalid");
878
879       MPlist plist = null;
880       using (FileStream stream = File.OpenRead (FileInfo.FullName))
881         plist = new MPlist (stream);
882       return plist;
883     }
884
885     public object Load (MSymbol key, MSymbol stop)
886     {
887       if (loader != null || Info.Format != MSymbol.plist)
888         throw new ArgumentException
889           ("Key can't be specified for loading this database");
890       if (! update_status ())
891         throw new Exception ("Database invalid");
892       MPlist plist = null;
893       using (FileStream stream = File.OpenRead (FileInfo.FullName))
894         plist = new MPlist (stream, key, stop);
895       return plist;
896     }
897
898     /// <summary>Return a list of currently available database
899     /// directory names</summary>.
900     public static string[] DirectoryList ()
901     {
902       List<string> dirs = new List<string> ();
903
904       for (int i = 1; i < 4; i++)
905         if (DBDirs[i].Dirname != null)
906           dirs.Add (DBDirs[i].Dirname);
907       return dirs.ToArray ();
908     }
909
910     // For IComparable<MDatabase>
911     public int CompareTo (MDatabase other)
912     {
913       return (ListIndex == other.ListIndex
914               ? DirIndex - other.DirIndex
915               : ListIndex - other.ListIndex);
916     }
917   }
918 }