*** 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 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       Console.WriteLine (share_dir);
433       Console.WriteLine (usr_dir);
434       try {
435         string dir = Environment.GetEnvironmentVariable ("M17NDIR");
436         DBDirs[1] = new MDatabaseDir (dir);
437       } catch {
438         try {
439           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
440         } catch (ArgumentException) {
441           DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
442         }
443       }
444       DBDirs[2] = new MDatabaseDir (null);
445       DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
446       update_all ();
447     }
448
449     public static string ApplicationDir
450     {
451       get { return (DBDirs[1].Dirname); }
452       set { DBDirs[2] = new MDatabaseDir (value); update_all (); }
453     }
454
455     private static bool update_all ()
456     {
457       bool updated = false;
458
459       for (int i = 1; i < 4; i++)
460         if (DBDirs[i].Dirname != null)
461           {
462             DBDirs[i].Refresh ();
463             if (LastUpdateTime < DBDirs[i].ListChangeTime)
464               {
465                 update_list (i);
466                 updated = true;
467               }
468             if (LastUpdateTime < DBDirs[i].DirChangeTime)
469               {
470                 update_dir (i);
471                 updated = true;
472               }
473           }
474       LastUpdateTime = DateTime.Now;
475       return updated;
476     }
477
478     public static void Dump ()
479     {
480       update_all ();
481       Console.WriteLine ("[DBDirs]");
482       for (int i = 1; i < 4; i++)
483         if (DBDirs[i].Dirname != null)
484           {
485             if (DBDirs[i].DirInfo != null)
486               {
487                 Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName);
488                 if (DBDirs[i].ListInfo != null)
489                   Console.WriteLine (" {0}", DBDirs[i].ListInfo);
490                 else
491                   Console.WriteLine (" .. no mdb.dir");
492               }
493             else
494               Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname);
495           }
496
497       Console.WriteLine ("[WDICT]");
498       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
499         foreach (MDatabase mdb in kv.Value)
500           Console.WriteLine (mdb);
501
502       Console.WriteLine ("[NDICT]");
503       foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
504         foreach (MDatabase mdb in kv.Value)
505           Console.WriteLine (mdb);
506     }
507
508     private static void register (MDatabase mdb)
509     {
510       Dictionary<MDatabase.Tag, List<MDatabase>> dict
511         = mdb.DBType == MDBType.WILDCARD ? wdict : ndict;
512       List<MDatabase> mdbs;
513
514       if (dict.TryGetValue (mdb.tag, out mdbs))
515         {
516           for (int i = 0; i < mdbs.Count; i++)
517             if (mdbs[i].ListIndex == mdb.ListIndex)
518               {
519                 mdbs[i].DBStatus = MDBStatus.INVALID;
520                 mdbs[i] = mdb;
521                 return;
522               }
523           mdbs.Add (mdb);
524         }
525       else
526         {
527           mdbs = new List<MDatabase> (1);
528           mdbs.Add (mdb);
529           dict.Add (mdb.tag, mdbs);
530         }
531     }
532
533     private static void register (int list_idx, int dir_idx,
534                                   Tag tag, MDatabaseInfo info)
535     {
536       Dictionary<MDatabase.Tag, List<MDatabase>> dict
537         = tag.HasWildcard ? wdict : ndict;
538       List<MDatabase> mdbs;
539       MDatabase mdb;
540
541       if (dict.TryGetValue (tag, out mdbs))
542         for (int i = 0; i < mdbs.Count; i++)
543           {
544             mdb = mdbs[i];
545             if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx)
546               {
547                 if (mdb.DBStatus == MDBStatus.INVALID)
548                   M17n.DebugPrint ("registering: {0}\n", mdb);
549                 else
550                   M17n.DebugPrint ("updating: {0}\n", mdb);
551                 mdb.DirIndex = dir_idx;
552                 if (dict == wdict)
553                   {
554                     mdb.DBType = MDBType.WILDCARD;
555                     mdb.DBStatus = MDBStatus.NOT_READY;
556                   }
557                 else if (dir_idx == -1)
558                   {
559                     mdb.DBType = MDBType.AUTO;
560                     mdb.DBStatus = MDBStatus.NOT_READY;
561                   }
562                 else
563                   {
564                     mdb.DBType = MDBType.MULTIPLE;
565                     mdb.DBStatus = MDBStatus.READY;
566                   }
567                 mdb.Info = info;
568                 return;
569               }
570           }
571       else
572         {
573           mdbs = new List<MDatabase> (1);
574           dict.Add (tag, mdbs);
575         }
576       mdb = new MDatabase (list_idx, dir_idx, tag, info);
577       M17n.DebugPrint ("registering: {0}\n", mdb);
578       mdbs.Add (mdb);
579     }
580
581     public MDatabase (Tag tag, Loader loader, object extra_info)
582     {
583       this.tag = tag;
584       this.loader = loader;
585       DBType = MDBType.UNKNOWN;
586       DBStatus = MDBStatus.READY;
587       DirIndex = 0;
588       ListIndex = 0;
589       ExtraInfo = extra_info;
590       register (this);
591     }
592
593     public MDatabase (Tag tag, string filename)
594     {
595       this.tag = tag;
596       DBType = MDBType.EXPLICIT;
597       if (Path.IsPathRooted (filename))
598         {
599           DirIndex = 0;
600           if (File.Exists (filename))
601             DBStatus = MDBStatus.READY;
602           else
603             DBStatus = MDBStatus.INVALID;
604         }
605       else
606         {
607           DirIndex = -1;
608           DBStatus = MDBStatus.NOT_READY;
609         }
610       ListIndex = 0;
611       Info = new MDatabaseInfo ();
612       Info.Filename = filename;
613       register (this);
614     }
615
616     private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info)
617     {
618       this.tag = tag;
619       this.Info = info;
620       DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO;
621       if (this.tag[0] == Mchar_table || this.tag[0] == Mcharset)
622         Info.Format = this.tag[0];
623       ListIndex = list_idx;
624       DirIndex = dir_idx;
625       if (Path.IsPathRooted (Info.Filename))
626         DBStatus = MDBStatus.READY;
627       else
628         DBStatus = MDBStatus.NOT_READY;
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       return (loader != null ? loader (tag, ExtraInfo)
871               : load (MSymbol.nil, MSymbol.nil));
872     }
873
874     public object Load (MSymbol key, MSymbol stop)
875     {
876       if (loader != null)
877         return null;
878       return load (key, stop);
879     }
880
881     private object load (MSymbol key, MSymbol stop)
882     {
883       return null;
884     }
885
886     public static string[] DirectoryList ()
887     {
888       List<string> dirs = new List<string> ();
889
890       for (int i = 1; i < 4; i++)
891         if (DBDirs[i].Dirname != null)
892           dirs.Add (DBDirs[i].Dirname);
893       return dirs.ToArray ();
894     }
895
896     // For IComparable<MDatabase>
897     public int CompareTo (MDatabase other)
898     {
899       return (ListIndex == other.ListIndex
900               ? DirIndex - other.DirIndex
901               : ListIndex - other.ListIndex);
902     }
903   }
904 }