*** empty log message ***
[m17n/m17n-lib-cs.git] / MDatabase.cs
index 9a1b3e6..afefa61 100644 (file)
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.IO;
+using System.Xml;
+
 using M17N;
 using M17N.Core;
 
 namespace M17N.Core
 {
-  public delegate object MDatabaseLoader (MDatabaseTag tag,
-                                         object extra_info);
-
-  public struct MDatabaseTag
+  internal class MGlob
   {
-    public MSymbol Tag0, Tag1, Tag2, Tag3;
-
-    public MDatabaseTag (MSymbol tag0)
-      {
-       Tag0 = tag0; Tag1 = Tag2 = Tag3 = MSymbol.nil;
-      }
+    static readonly char sep = Path.DirectorySeparatorChar;
 
-    public MDatabaseTag (MSymbol tag0, MSymbol tag1)
-      {
-       Tag0 = tag0; Tag1 = tag1; Tag2 = Tag3 = MSymbol.nil;
-      }
+    public static void FileList (ref List<FileInfo> files,
+                                string dir, string pattern)
+    {
+      int len = pattern.Length;
 
-    public MDatabaseTag (MSymbol tag0, MSymbol tag1, MSymbol tag2)
-      {
-       Tag0 = tag0; Tag1 = tag1; Tag2 = tag2; Tag3 = MSymbol.nil;
-      }
+      if (Path.IsPathRooted (pattern))
+       {
+         int headsep = 0;
+         int i;
 
-    public MDatabaseTag (MSymbol tag0, MSymbol tag1,
-                        MSymbol tag2, MSymbol tag3)
-      {
-       Tag0 = tag0; Tag1 = tag1; Tag2 = tag2; Tag3 = tag3;
-      }
-  }
+         for (i = 1; i < len && (pattern[i] != '*' && pattern[i] != '?'); i++)
+           if (pattern[i] == sep)
+             headsep = i;
+         if (i == len)
+           {
+             if (File.Exists (pattern))
+               files.Add (new FileInfo (pattern));
+             return;
+           }
+         dir = pattern.Substring (0, headsep);
+         pattern = pattern.Substring (headsep + 1);
+       }
+      else
+       {
+         if (dir == null)
+           dir = Directory.GetCurrentDirectory ();
+       }
+      if (Directory.Exists (dir))
+       list (ref files, new DirectoryInfo (dir), pattern);
+    }
 
-  public class MDatabase
-  {
-    private class MDatabaseDir
+    private static void list (ref List<FileInfo> files,
+                             DirectoryInfo dirinfo, string pattern)
     {
-      private const string ListFileName = "mdb.dir";
-
-      public string Dirname;
-      public DirectoryInfo DirInfo;
-      public DateTime LastScanned;
-      public FileInfo ListInfo;
-
-      private static void GetInfo (string dirname, out DirectoryInfo dirinfo,
-                                  out FileInfo listinfo)
-      {
-       if (Directory.Exists (dirname))
-         try { dirinfo = new DirectoryInfo (dirname);
-           try { listinfo = dirinfo.GetFiles (ListFileName)[0];
-           } catch { listinfo = null; }
-         } catch { dirinfo = null; listinfo = null; }
+      int len = pattern.Length;
+      int i;
+    
+      M17n.DebugPrint ("Listing {0} in {1} ...", pattern, dirinfo);
+      for (i = 0; i < len && pattern[i] != sep; i++);
+      try {
+       if (i == len)
+         {
+           FileInfo[] listing = dirinfo.GetFiles (pattern);
+           i = listing.Length;
+           foreach (FileInfo elt in listing)
+             files.Add (elt);
+         }
        else
          {
-           dirinfo = null;
-           listinfo = null;
+           string tail = pattern.Substring (i + 1);
+           pattern = pattern.Substring (0, i);
+           DirectoryInfo[] listing = dirinfo.GetDirectories (pattern);
+
+           i = listing.Length;
+           foreach (DirectoryInfo elt in listing)
+             list (ref files, elt, tail);
          }
+      } catch {
       }
+      M17n.DebugPrint (" found {0} files\n", i);
+    }
+  }
 
-      public MDatabaseDir (string dirname)
-      {
-       Dirname = dirname;
-       GetInfo (dirname, out DirInfo, out ListInfo);
-      }
+  internal class MDatabaseDir
+  {
+    private const string ListFileName = "mdb.dir";
 
-      public bool StatusChanged {
-       get {
-         bool exists = Directory.Exists (Dirname);
+    public string Dirname;
+    public DirectoryInfo DirInfo;
+    public DateTime DirChangeTime;
+    public FileInfo ListInfo;
+    public DateTime ListChangeTime;
 
-         if (DirInfo != null)
+    public MDatabaseDir (string dirname)
+    {
+      Dirname = dirname;
+      DirChangeTime = ListChangeTime = DateTime.Now;
+    }
+
+    public void Refresh ()
+    {
+      if (DirInfo != null)
+       {
+         if (Dirname != null && Directory.Exists (Dirname))
            {
-             if (! exists)
-               {
-                 DirInfo = null;
-                 ListInfo = null;
-                 LastScanned = new DateTime (0);
-               }
-             if (LastScanned.Year == 0)
-               return true;
              DirInfo.Refresh ();
-             if (ListInfo != null)
-               ListInfo.Refresh ();
-             return (LastScanned < DirInfo.LastWriteTime
-                     || LastScanned < ListInfo.LastWriteTime);
+             if (DirChangeTime < DirInfo.LastWriteTime)
+               DirChangeTime = DirInfo.LastWriteTime;
+           }
+         else
+           {
+             DirInfo = null;
+             DirChangeTime = DateTime.Now;
+           }
+       }
+      else
+       {
+         if (Dirname != null && Directory.Exists (Dirname))
+           {
+             DirInfo = new DirectoryInfo (Dirname);
+             DirChangeTime = DateTime.Now;
+           }
+       }
+      if (DirInfo == null)
+       {
+         if (ListInfo != null)
+           {
+             ListInfo = null;
+             ListChangeTime = DateTime.Now;
+           }
+       }
+      else
+       {
+         if (ListInfo != null)
+           {
+             ListInfo.Refresh ();
+             if (ListChangeTime < ListInfo.LastWriteTime)
+               ListChangeTime = ListInfo.LastWriteTime;
            }
          else
            {
-             if (exists)
+             try {
+               ListInfo = DirInfo.GetFiles (ListFileName)[0];
+               ListChangeTime = DateTime.Now;
+             } catch {
+             }
+           }
+       }
+    }
+  }
+
+  public partial class MDatabase : IComparable<MDatabase>
+  {
+    /// Identifier of a MDatabase.
+    public struct Tag : IEquatable<Tag>
+    {
+      private MSymbol[] Tags;
+
+      public Tag (MSymbol tag0)
+       {
+         Tags = new MSymbol[4];
+         Tags[0] = tag0; Tags[1] = Tags[2] = Tags[3] = MSymbol.nil;
+       }
+
+      public Tag (MSymbol tag0, MSymbol tag1)
+       {
+         Tags = new MSymbol[4];
+         Tags[0] = tag0; Tags[1] = tag1; Tags[2] = Tags[3] = MSymbol.nil;
+       }
+
+      public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2)
+       {
+         Tags = new MSymbol[4];
+         Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = MSymbol.nil;
+       }
+
+      public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
+       {
+         Tags = new MSymbol[4];
+         Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = tag3;
+       }
+
+      public Tag (ref MPlist plist)
+       {
+         Tags = new MSymbol[4];
+
+         for (int i = 0; i < 4; i++)
+           {
+             if (plist.IsSymbol)
                {
-                 DirInfo = new DirectoryInfo (Dirname);
-                 try {
-                   ListInfo = DirInfo.GetFiles (ListFileName)[0];
-                 } catch {
-                   ListInfo = null;
-                 }
-                 return true;
+                 Tags[i] = plist.Symbol;
+                 plist = plist.Next;
                }
-             return false;
+             else
+               Tags[i] = MSymbol.nil;
            }
        }
+
+      public bool Equals (Tag tag)
+      {
+       for (int i = 0; i < 4; i++)
+         if (tag[i] != Tags[i])
+           return false;
+       return true;
       }
 
-      public void UpdateStatus ()
+      public override int GetHashCode ()
       {
-       if (DirInfo != null)
-         LastScanned = DateTime.UtcNow;
+       return (Tags[0].GetHashCode () ^ Tags[1].GetHashCode ()
+               ^ Tags[2].GetHashCode () ^ Tags[3].GetHashCode ());
       }
 
-      public FileInfo[] Scan (string filename)
+      public override string ToString ()
       {
-       if (DirInfo == null)
-         return null;
-       DirInfo.Refresh ();
-       return DirInfo.GetFiles (filename);
+       return ("<"
+               + Tags[0] + "," + Tags[1] + "," + Tags[2] + "," + Tags[3]
+               + ">");
+      }
+
+      public MSymbol this[int i]
+      {
+       set { Tags[i] = value; }
+       get { return Tags[i]; }
+      }
+
+      public bool Match (Tag tag)
+      {
+       for (int i = 0; i < 4; i++)
+         {
+           if (tag[i] == Mwildcard || Tags[i] == Mwildcard)
+             return true;
+           if (tag[i] != Tags[i])
+             return false;
+         }
+       return true;
+      }
+
+      public bool HasWildcard {
+       get {
+         for (int i = 0; i < 4; i++)
+           if (Tags[i] == Mwildcard)
+             return true;
+         return false;
+       }
       }
     }
 
-    internal class MDatabaseInfo {
-      internal DirectoryInfo Dir;
+    public delegate object Loader (Tag tag, object extra_info);
+
+    internal class MDatabaseInfo
+    {
+      // These come from the declartion plist of the database.
       internal string Description;
-      internal MText Filename;
-      internal FileInfo FileInfo;
+      internal string Filename;
       internal FileInfo Validater;
       internal int Version;
       internal MSymbol Format;
       internal MSymbol Schema;
-      internal MText SchemaFile;
-      internal DateTime ModifiedTime;
+      internal string SchemaFile;
       internal MPlist Props;
+
+      public MDatabaseInfo ()
+      {
+       Format = Schema = MSymbol.nil;
+      }
+
+      private static int parse_version (MPlist plist)
+      {
+       string[] str;
+       int major, minor, release;
+
+       if (! plist.IsMText)
+         return 0xFFFFFF;
+       str = plist.Text.ToString ().Split ('.');
+       if (str.Length != 3)
+         return 0xFFFFFF;
+       try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; }
+       try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; }
+       try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; }
+       return ((major << 16) | (minor << 8) | release);
+      }
+
+      public MDatabaseInfo (MPlist plist)
+      {
+       Format = MSymbol.nil;
+       if (plist.IsMText)
+         {
+           Format = MSymbol.plist;
+           Filename = plist.Text.ToString ();
+           plist = plist.Next;
+         }
+       else if (plist.IsPlist)
+         {
+           MPlist p = plist.Plist;
+
+           if (p.IsMText)
+             {
+               Filename = p.Text.ToString ();
+               p = p.Next;
+             }
+           if (p.IsSymbol)
+             {
+               Format = p.Symbol;
+               p = p.Next;
+             }
+           if (p.IsSymbol)
+             {
+               Schema = p.Symbol;
+               p = p.Next;
+             }
+           if (p.IsMText)
+             SchemaFile = p.Text.ToString ();
+           plist = plist.Next;
+         }
+
+       Version = 0;
+       Props = new MPlist ();
+       foreach (MPlist pl in plist)
+         {
+           if (pl.IsPlist)
+             {
+               MPlist p = pl.Plist;
+             
+               if (p.IsSymbol && p.Symbol == Mversion)
+                 Version = parse_version (p.Next);
+               else
+                 Props.Put (pl.Key, pl.Val);
+             }
+           else if (pl.IsSymbol)
+             {
+               MPlist p = new MPlist ();
+               p.Add (MSymbol.symbol, pl.Symbol);
+               p.Add (MSymbol.symbol, MSymbol.t);
+               Props.Put (MSymbol.plist, p);
+             }
+         }
+      }
+
+      public void Merge (MDatabaseInfo src)
+      {
+       if (Validater == null)
+         Validater = src.Validater;
+       if (Version == 0)
+         Version = src.Version;
+       if (Format == MSymbol.nil)
+         Format = src.Format;
+       if (Schema == MSymbol.nil)
+         Schema = src.Schema;
+       if (SchemaFile == null)
+         SchemaFile = src.SchemaFile;
+       foreach (MPlist p in src.Props)
+         if (Props.Assq (p.Plist.Symbol) == null)
+           Props.Push (p.Key, p.Val);
+      }
+
+      public override string ToString ()
+      {
+       string str = ("#<Info " + Format + " \"" + Filename + "\"");
+       if (Schema != MSymbol.nil)
+         str += " " + Schema;
+       return str + ">";
+      }
     }
 
-    private static Dictionary<MDatabaseTag, MDatabase> DBDict
-      = new Dictionary<MDatabaseTag, MDatabase> ();
+    // Dictionaries for normal databases.
+    private static Dictionary<MDatabase.Tag, List<MDatabase>> ndict
+      = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
+
+    // Dictionaries for databases of DBType WILDCARD
+    private static Dictionary<MDatabase.Tag, List<MDatabase>> wdict
+      = new Dictionary<MDatabase.Tag, List<MDatabase>> ();
+
+    private static MDatabaseDir[] DBDirs = new MDatabaseDir[4];
 
-    private static MDatabaseDir[] DBDirs = new MDatabaseDir[3];
+    private static DateTime LastUpdateTime = new DateTime (0);
 
-    private const string SystemDirectory = "/usr/share/m17n";
-    private readonly MSymbol Mversion = new MSymbol ("version");
+    private static readonly MSymbol Mversion = "version";
+    private static readonly MSymbol Mwildcard = "*";
+    private static readonly MSymbol Mchar_table = "char-table";
+    private static readonly MSymbol Mcharset = "charset";
+    private static readonly MSymbol Mxml = "xml";
+
+    private static TimeSpan CheckInterval = new TimeSpan (50000000);
 
     /// Type of database
     private enum MDBType
       {
-       /// The database was defined automatically from mdb.dir
-       /// file(s) with no wildcard tag.
+       /// The database was defined automatically from one of mdb.dir
+       /// files with no wildcard tag.
        AUTO,
-       /// The database was defined automatically from mdb.dir
-       /// file(s) with a wildcard tag to define multiple databases
+       /// The database was defined automatically from one of mdb.dir
+       /// files with a wildcard tag to define multiple databases
        /// of the same kind.
        MULTIPLE,
        /// The database was defined explicitely by MDatabase.Define
-       /// without a special loader.
+       /// to use the normal loader.
        EXPLICIT,
        /// The database was defined explicitely by MDatabase.Define
-       /// with a special loader.
+       /// to use a special loader.
        UNKNOWN,
+       /// The database is for defining multiple databases of the
+       /// same kind with a wildcard tag.
+       WILDCARD,
       };
 
     /// Status of database
     private enum MDBStatus
       {
-       // The database file is currently disabled.  It means that the
-       // database file is not readable or the database is deleted by
-       // the modification of "mdb.dir".
+       // The database file has not yet been decided, or is not yet
+       // expanded if DBType is WILDCARD.
+       NOT_READY,
+       // The database file was decided, or has been expanded if
+       // DBType is WILDCARD.
+       READY,
+       // The database is disabled.  It means that the database file
+       // is not readable, the version is not supported by the
+       // current system, or the validation was failed.
        DISABLED,
-       // The database file has not yet been loaded, or was modified
-       // after the previous loading.
-       OUTDATED,
-       // The database file has not been modified after the previous
-       // loading.
-       UPDATED,
-       // The database file is updated but the validation was failed
-       // or the version is not supported by the current system.
+       // The database is deleted by the modificaiton of mdb.dir, or
+       // is overwritten by a new explicit definition.
        INVALID,
       };
 
-    public readonly MDatabaseTag Tag;
-    private MDatabaseLoader Loader;
+    public Tag tag;
+    public NameTable name_table = new NameTable ();
+    private Loader loader;
     private object ExtraInfo;
+    // Directory of the database file.
+    // -1:unknown, 0:absolute, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
+    private int DirIndex;
+    // Directory of the mdb.dir defining the database file.
+    // 0: EXPLICIT or UNKNOWN, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3]
+    private int ListIndex;
     private MDBType DBType;
     private MDBStatus DBStatus;
-    internal DateTime LoadedTime;
     internal MDatabaseInfo Info;
+    // File in which the database contents is stored.  This is null
+    // when DBStatus is NOT_READY.
+    internal FileInfo FileInfo;
+    // When the database file is loaded last.
+    internal DateTime LastLoaded = DateTime.Now;
+
+    public enum LoadStatus
+    {
+      None,
+      InvalidLoadMethod,
+      NotAvailable,
+      NotReadable,
+      InvalidContents,
+    };
+
+    public LoadStatus LastLoadStatus = LoadStatus.None;
+
+    static MDatabase ()
+    {
+      string share_dir = (Environment.GetFolderPath
+                         (Environment.SpecialFolder.CommonApplicationData));
+      string usr_dir = (Environment.GetFolderPath
+                       (Environment.SpecialFolder.ApplicationData));
+
+      try {
+       string dir = Environment.GetEnvironmentVariable ("M17NDIR");
+       DBDirs[1] = new MDatabaseDir (dir);
+      } catch {
+       try {
+         DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
+       } catch (ArgumentException) {
+         DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
+       }
+      }
+      DBDirs[2] = new MDatabaseDir (null);
+      DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
+      update_all (true);
+    }
+
+    public static string ApplicationDir
+    {
+      get { return (DBDirs[1].Dirname); }
+      set { DBDirs[2] = new MDatabaseDir (value); update_all (true); }
+    }
+
+    // Update all listing and directories.  Return true iff some are
+    // really updated.
+    private static bool update_all (bool force)
+    {
+      if (! force && DateTime.Now - LastUpdateTime < CheckInterval)
+       return false;
+
+      bool updated = false;
+      for (int i = 1; i < 4; i++)
+       if (DBDirs[i].Dirname != null)
+         {
+           DBDirs[i].Refresh ();
+           if (LastUpdateTime < DBDirs[i].ListChangeTime)
+             {
+               update_list (i);
+               updated = true;
+             }
+           if (LastUpdateTime < DBDirs[i].DirChangeTime)
+             {
+               update_dir (i);
+               updated = true;
+             }
+         }
+      if (updated)
+       LastUpdateTime = DateTime.Now;
+      return updated;
+    }
+
+    public static void Dump ()
+    {
+      update_all (false);
+      Console.WriteLine ("[DBDirs]");
+      for (int i = 1; i < 4; i++)
+       if (DBDirs[i].Dirname != null)
+         {
+           if (DBDirs[i].DirInfo != null)
+             {
+               Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName);
+               if (DBDirs[i].ListInfo != null)
+                 Console.WriteLine (" {0}", DBDirs[i].ListInfo);
+               else
+                 Console.WriteLine (" .. no mdb.dir");
+             }
+           else
+             Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname);
+         }
 
-    public static string ApplicationDirectory;
+      Console.WriteLine ("[WDICT]");
+      foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
+       foreach (MDatabase mdb in kv.Value)
+         Console.WriteLine (mdb);
 
-    private MDatabase (MDatabaseTag tag, MDatabaseLoader loader,
-                     object extra_info)
+      Console.WriteLine ("[NDICT]");
+      foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
+       foreach (MDatabase mdb in kv.Value)
+         Console.WriteLine (mdb);
+    }
+
+    private static void register (MDatabase mdb)
+    {
+      Dictionary<MDatabase.Tag, List<MDatabase>> dict
+       = mdb.DBType == MDBType.WILDCARD ? wdict : ndict;
+      List<MDatabase> mdbs;
+
+      if (dict.TryGetValue (mdb.tag, out mdbs))
+       {
+         for (int i = 0; i < mdbs.Count; i++)
+           if (mdbs[i].ListIndex == mdb.ListIndex)
+             {
+               mdbs[i].DBStatus = MDBStatus.INVALID;
+               mdbs[i] = mdb;
+               return;
+             }
+         mdbs.Add (mdb);
+       }
+      else
+       {
+         mdbs = new List<MDatabase> (1);
+         mdbs.Add (mdb);
+         dict.Add (mdb.tag, mdbs);
+       }
+    }
+
+    private static void register (int list_idx, int dir_idx,
+                                 Tag tag, MDatabaseInfo info)
+    {
+      Dictionary<MDatabase.Tag, List<MDatabase>> dict
+       = tag.HasWildcard ? wdict : ndict;
+      List<MDatabase> mdbs;
+      MDatabase mdb;
+
+      if (dict.TryGetValue (tag, out mdbs))
+       for (int i = 0; i < mdbs.Count; i++)
+         {
+           mdb = mdbs[i];
+           if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx)
+             {
+               mdb.DirIndex = dir_idx;
+               if (dict == wdict)
+                 {
+                   mdb.DBType = MDBType.WILDCARD;
+                   mdb.DBStatus = MDBStatus.NOT_READY;
+                 }
+               else if (dir_idx == -1)
+                 {
+                   mdb.DBType = MDBType.AUTO;
+                   mdb.DBStatus = MDBStatus.NOT_READY;
+                 }
+               else
+                 {
+                   mdb.DBType = MDBType.MULTIPLE;
+                   mdb.DBStatus = MDBStatus.READY;
+                 }
+               mdb.Info = info;
+               if (mdb.DBStatus == MDBStatus.INVALID)
+                 M17n.DebugPrint ("registering: {0}\n", mdb);
+               else
+                 M17n.DebugPrint ("updating: {0}\n", mdb);
+               return;
+             }
+         }
+      else
+       {
+         mdbs = new List<MDatabase> (1);
+         dict.Add (tag, mdbs);
+       }
+      mdb = new MDatabase (list_idx, dir_idx, tag, info);
+      M17n.DebugPrint ("registering: {0}\n", mdb);
+      mdbs.Add (mdb);
+    }
+
+    public MDatabase (Tag tag, Loader loader, object extra_info)
     {
-      Tag = tag;
-      Loader = loader;
+      this.tag = tag;
+      this.loader = loader;
+      DBType = MDBType.UNKNOWN;
+      DBStatus = MDBStatus.READY;
+      DirIndex = 0;
+      ListIndex = 0;
       ExtraInfo = extra_info;
+      register (this);
     }
 
-    private MDatabase (MDatabaseTag tag, string filename)
+    public MDatabase (Tag tag, string filename)
     {
-      Tag = tag;
+      this.tag = tag;
+      DBType = MDBType.EXPLICIT;
+      if (Path.IsPathRooted (filename))
+       {
+         DirIndex = 0;
+         if (File.Exists (filename))
+           DBStatus = MDBStatus.READY;
+         else
+           DBStatus = MDBStatus.INVALID;
+       }
+      else
+       {
+         DirIndex = -1;
+         DBStatus = MDBStatus.NOT_READY;
+       }
+      ListIndex = 0;
       Info = new MDatabaseInfo ();
-      Info.Filename = new MText (filename);
+      Info.Filename = filename;
+      register (this);
     }
 
-    private MDatabase (MPlist plist)
+    private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info)
     {
-      MSymbol[] tags = new MSymbol[4];
-      int i;
+      this.tag = tag;
+      this.Info = info;
+      DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO;
+      if (tag[0] == Mchar_table || tag[0] == Mcharset)
+       Info.Format = tag[0];
+      ListIndex = list_idx;
+      DirIndex = dir_idx;
+      if (Path.IsPathRooted (Info.Filename))
+       DBStatus = MDBStatus.READY;
+      else
+       DBStatus = MDBStatus.NOT_READY;
+      if (Info.Format == Mchar_table)
+       MCharProp.Define (tag[2], this);
+    }
+
+    public override String ToString () {
+      string str = ("#<MDataBase (" + ListIndex + "," + DirIndex + ") "
+                   + tag + " " + DBType + " " + DBStatus);
+
+      if (DBType != MDBType.EXPLICIT && DBType != MDBType.UNKNOWN)
+       str += " " + Info;
+      return str + ">";
+    }
+
+    // Update (or disable) databases defined by "mdb.dir" in
+    // DBDirs[list_idx].
+    private static void update_list (int list_idx)
+    {
+      M17n.DebugPrint ("Updating list: {0}\n", list_idx);
+      // At first disable all target databases.
+      foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
+       foreach (MDatabase mdb in kv.Value)
+         if (mdb.ListIndex == list_idx)
+           {
+             M17n.DebugPrint ("deleting: {0}\n", mdb);
+             mdb.DBStatus = MDBStatus.INVALID;
+             break;
+           }
+      foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
+       foreach (MDatabase mdb in kv.Value)
+         if (mdb.ListIndex == list_idx)
+           {
+             M17n.DebugPrint ("deleting: {0}\n", mdb);
+             mdb.DBStatus = MDBStatus.INVALID;
+             break;
+           }
+
+      FileInfo dblist = DBDirs[list_idx].ListInfo;
+      if (dblist == null)
+       return;
 
-      for (i = 0; plist.IsSymbol; i++, plist = plist.Next)
-       tags[i] = plist.Symbol;
-      while (i < 4)
-       tags[i++] = MSymbol.nil;
-      Tag = new MDatabaseTag (tags[0], tags[1], tags[2], tags[3]);
-      if (plist.IsMText)
+      MPlist plist = null;
+      using (FileStream stream = File.OpenRead (dblist.FullName))
        {
-         Info.Filename = plist.Text;
-         plist = plist.Next;
+         plist = new MPlist (stream);
        }
-      else if (plist.IsPlist)
+      if (plist == null)
+       return;
+      foreach (MPlist pl in plist)
+       if (pl.IsPlist)
+         {
+           try
+             {
+               MPlist p = pl.Plist;
+               Tag tag = new Tag (ref p);
+               MDatabaseInfo info = new MDatabaseInfo (p);
+               int dir_idx = Path.IsPathRooted (info.Filename) ? 0 : -1;
+               register (list_idx, dir_idx, tag, info);
+             }
+           catch (Exception e)
+             {
+               Console.WriteLine (e.Message + ": " + pl.Plist);
+             }
+         }
+    }
+
+    // Update (or disable) databases in DBDirs[dir_idx].
+    private static void update_dir (int dir_idx)
+    {
+      M17n.DebugPrint ("Updating dir: {0}\n", dir_idx);
+      // Reset all databases in DBDirs[dir_idx].
+      foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
+       foreach (MDatabase mdb in kv.Value)
+         if (mdb.DirIndex >= dir_idx)
+           {
+             M17n.DebugPrint ("disabling: {0}\n", mdb);
+             mdb.DBStatus = MDBStatus.NOT_READY;
+             mdb.DirIndex = -1;
+           }
+      // Re-expand all WILDCARD databases in DBDirs[dir_idx].
+      if (DBDirs[dir_idx].DirInfo != null)
+       foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
+         foreach (MDatabase mdb in kv.Value)
+           if (mdb.DBStatus == MDBStatus.READY)
+             {
+               M17n.DebugPrint ("re-expanding: {0}\n", mdb);
+               register_files (DBDirs[dir_idx].Dirname, dir_idx, mdb);
+             }
+    }
+
+    private static bool parse_plist_header (FileInfo fileinfo, MDatabase mdb,
+                                           out Tag tag, out MDatabaseInfo info)
+    {
+      MPlist plist = null;
+
+      tag = new Tag (MSymbol.nil);
+      info = null;
+      using (FileStream stream = fileinfo.OpenRead ())
        {
-         MPlist p = plist.Plist;
+         try { plist = new MPlist (stream, 1); } catch { }
+       }
+      if (plist == null || ! plist.IsPlist)
+       return false;
+      plist = plist.Plist;
+      tag = new Tag (ref plist);
+      if (tag.HasWildcard || ! tag.Match (mdb.tag))
+       return false;
+      info = new MDatabaseInfo (plist);
+      return true;
+    }
+
+    private static bool parse_xml_header (FileInfo fileinfo, MDatabase mdb,
+                                         out Tag tag, out MDatabaseInfo info)
+    {
+      tag = new Tag (MSymbol.nil);
+      info = null;
+      using (FileStream stream = fileinfo.OpenRead ())
+       {
+         try {
+           MPlist plist = new MPlist ();
+           XmlTextReader reader = new XmlTextReader (stream);
+           
+           reader.WhitespaceHandling = WhitespaceHandling.None;
+           do {
+             reader.Read ();
+           } while (reader.NodeType != XmlNodeType.Element);
+           plist.Add (MSymbol.symbol, (MSymbol) reader.Name);
+           reader.Read ();
+           if (reader.NodeType == XmlNodeType.Element && reader.Name == "tags")
+             {
+               reader.Read ();
+               while (reader.NodeType == XmlNodeType.Element)
+                 {
+                   reader.Read ();
+                   plist.Add (MSymbol.symbol, (MSymbol) reader.Value);
+                   reader.Read ();
+                   reader.Read ();
+                 }
+               tag = new Tag (ref plist);
+               if (tag.HasWildcard || ! tag.Match (mdb.tag))
+                 return false;
+               info = new MDatabaseInfo (plist);
+               return true;
+             }
+         } catch (Exception e) {
+           Console.WriteLine ("error {0}", e);
+         }
+       }
+      return false;
+    }
 
-         if (p.IsMText)
-           Info.Filename = plist.Text;
-         p = p.Next;
-         if (! p.IsEmpty)
+    private static void register_files (string dir, int dir_idx, MDatabase mdb)
+    {
+      int list_idx = mdb.ListIndex;
+      List<FileInfo> files = new List<FileInfo> ();
+      MGlob.FileList (ref files, dir, mdb.Info.Filename);
+      foreach (FileInfo fileinfo in files)
+       {
+         Tag tag;
+         MDatabaseInfo info;
+
+         if (mdb.Info.Format == MSymbol.plist
+             ? parse_plist_header (fileinfo, mdb, out tag, out info)
+             : parse_xml_header (fileinfo, mdb, out tag, out info))
            {
-             if (p.IsSymbol)
-               Info.Format = p.Symbol;
-             p = p.Next;
-             if (! p.IsEmpty)
-               {
-                 if (p.IsSymbol)
-                   Info.Schema = p.Symbol;
-                 p = p.Next;
-                 if (p.IsMText)
-                   Info.SchemaFile = p.Text;
-               }                   
+             info.Merge (mdb.Info);
+             if (Path.IsPathRooted (mdb.Info.Filename))
+               info.Filename = fileinfo.FullName;
+             else
+               info.Filename = fileinfo.Name;
+             register (list_idx, dir_idx, tag, info);
            }
-         plist = plist.Next;
        }
-      DBStatus = MDBStatus.OUTDATED;;
-      Info.Version = 0;
-      Info.Props = new MPlist ();
-      foreach (MPlist pl in plist)
+    }
+
+    private void expand_wildcard ()
+    {
+      M17n.DebugPrint ("expanding: {0}\n", this);
+
+      if (DirIndex == 0)
+       register_files (null, DirIndex, this);
+      else
+       for (int i = 1; i < 4; i++)
+         if (DBDirs[i].DirInfo != null)
+           register_files (DBDirs[i].DirInfo.FullName, i, this);
+      DBStatus = MDBStatus.READY;
+    }
+
+    private static void maybe_expand_wildcard (Tag tag)
+    {
+      foreach (KeyValuePair<Tag, List<MDatabase>> kv in wdict)
        {
-         if (pl.IsPlist)
+         M17n.DebugPrint ("expand check: {0}\n", kv.Key);
+         if (kv.Key.Match (tag))
            {
-             MPlist p = pl.Plist;
-             
-             if (p.IsSymbol && p.Symbol == Mversion)
+             foreach (MDatabase mdb in kv.Value)
                {
-                 Info.Version = parse_version (p.Next);
-                 if (M17n.Version < Info.Version)
-                   DBStatus = MDBStatus.DISABLED;
+                 if (mdb.DBStatus == MDBStatus.NOT_READY)
+                   mdb.expand_wildcard ();
                }
            }
-         Info.Props.Put (pl.Key, pl.Val);
        }
     }
 
-    private static int parse_version (MPlist plist)
-    {
-      string[] str;
-      int major, minor, release;
+    // Update the status.  Return true iff the database file is
+    // readable but changed.
 
-      if (! plist.IsMText)
-       return 0xFFFFFF;
-      str = plist.Text.ToString ().Split ('.');
-      if (str.Length != 3)
-       return 0xFFFFFF;
-      try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; }
-      try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; }
-      try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; }
-      return ((major << 16) | (minor << 8) | release);
+    private bool update_status ()
+    {
+      if (DBType == MDBType.UNKNOWN)
+       return true;
+      update_all (false);
+      if (DBStatus == MDBStatus.INVALID)
+       return false;
+      if (DBStatus != MDBStatus.NOT_READY)
+       {
+         try {
+           FileInfo.Refresh ();
+         } catch {
+           DBStatus = MDBStatus.INVALID;
+           return false;
+         }
+         if (LastLoaded >= FileInfo.LastWriteTime)
+           return false;
+         DBStatus = MDBStatus.READY;
+         return true;
+       }         
+      for (int i = 1; i < 4; i++)
+       if (DBDirs[i] != null && DBDirs[i].Dirname != null)
+         {
+           string filename = Path.Combine (DBDirs[i].Dirname, Info.Filename);
+           if (File.Exists (filename))
+             {
+               FileInfo = new FileInfo (filename);
+               DirIndex = i;
+               DBStatus = MDBStatus.READY;
+               return true;
+             }
+         }
+      return false;
     }
 
-    public static MDatabase Define (MDatabaseTag tag, MDatabaseLoader loader,
-                                   object extra_info)
+    public static MDatabase Find (Tag tag)
     {
-      MDatabase db = MDatabase.Find (tag);
+      List<MDatabase> mdbs;
+      MDatabase mdb = null;
+
+      if (tag.HasWildcard)
+       throw new ArgumentException ("Wildcard not allowed: " + tag);
 
-      if (db != null)
+      if (ndict.TryGetValue (tag, out mdbs))
+       {
+         mdbs.Sort ();
+         for (int i = 0; i < mdbs.Count; i++)
+           {
+             mdb = mdbs[i];
+             if (mdb.ListIndex == 0)
+               return mdb;
+             if (mdb.DBStatus == MDBStatus.READY)
+               break;
+           }
+       }
+      if (! update_all (false) && mdb != null)
+       return mdb;
+      maybe_expand_wildcard (tag);
+      if (! ndict.TryGetValue (tag, out mdbs))
+       return null;
+      for (int i = 0; i < mdbs.Count; i++)
        {
-         db.Loader = loader;
-         db.ExtraInfo = extra_info;
-         db.DBType = MDBType.EXPLICIT;
-         db.DBStatus = MDBStatus.OUTDATED;
-         db.Info = null;
-         return db;
+         mdb = mdbs[i];
+         mdb.update_status ();
+         if (mdb.DBStatus == MDBStatus.READY)
+           return mdb;
        }
-      return new MDatabase (tag, loader, extra_info);
+      return null;
     }
 
-    public static  MDatabase Define (MDatabaseTag tag, string filename)
+    public static List<MDatabase> List (Tag tag)
     {
-      MDatabase db = MDatabase.Find (tag);
+      List<MDatabase> list = new List<MDatabase> ();
 
-      if (db != null)
-       {
-         db.Loader = null;
-         db.DBType = MDBType.EXPLICIT;
-         db.DBStatus = MDBStatus.OUTDATED;
-         db.Info = new MDatabaseInfo ();
-         db.Info.Filename = new MText (filename);
+      update_all (false);
+      maybe_expand_wildcard (tag);
 
-         return db;
+      if (tag.HasWildcard)
+       {
+         foreach (KeyValuePair<Tag, List<MDatabase>> kv in ndict)
+           if (kv.Key.Match (tag))
+             foreach (MDatabase mdb in kv.Value)
+               {
+                 mdb.update_status ();
+                 if (mdb.DBStatus == MDBStatus.READY)
+                   {
+                     list.Add (mdb);
+                     break;
+                   }
+               }
+       }
+      else
+       {
+         List<MDatabase> mdbs;
+         if (ndict.TryGetValue (tag, out mdbs))
+           foreach (MDatabase mdb in mdbs)
+             {
+               mdb.update_status ();
+               if (mdb.DBStatus == MDBStatus.READY)
+                 {
+                   list.Add (mdb);
+                   break;
+                 }
+             }
        }
-      return new MDatabase (tag, filename);
+      return list;
     }
 
-    static MDatabase ()
+    private FileStream get_stream ()
     {
-      string share_dir = (Environment.GetFolderPath
-                         (Environment.SpecialFolder.CommonApplicationData));
-      string usr_dir = (Environment.GetFolderPath
-                       (Environment.SpecialFolder.ApplicationData));
+      if (loader != null
+         || (Info.Format != MSymbol.plist && Info.Format != Mxml))
+       {
+         LastLoadStatus = LoadStatus.InvalidLoadMethod;
+         return null;
+       }
+      if (DBStatus != MDBStatus.READY)
+       {
+         LastLoadStatus = LoadStatus.NotAvailable;
+         return null;
+       }
 
+      FileStream stream = null;
       try {
-       DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
-      } catch (ArgumentException) {
-       DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
+       stream = FileInfo.OpenRead ();
+      } catch {
+       LastLoadStatus = LoadStatus.NotReadable;          
       }
-      DBDirs[1] = null;
-      DBDirs[2] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
+      return stream;
     }
 
-    private static void Update ()
+    public object Load ()
     {
+      if (loader != null)
+       return loader (tag, ExtraInfo);
+      if (Info.Format == Mxml)
+       {
+         XmlDocument doc = new XmlDocument (name_table);
+         try {
+           XmlTextReader reader
+             = new XmlTextReader (FileInfo.FullName, name_table);
+           doc.Load (reader);
+           LastLoaded = DateTime.Now;      
+         } catch (Exception e) {
+           Console.WriteLine (e);
+           LastLoadStatus = LoadStatus.InvalidContents;
+         }
+         return doc;
+       }
 
+      FileStream stream = get_stream ();
+      if (stream == null)
+       return null;
+      MPlist plist = null;
+      try {
+       plist = new MPlist (stream);
+       LastLoaded = DateTime.Now;
+      } catch {
+       LastLoadStatus = LoadStatus.InvalidContents;
+      } finally {
+       stream.Dispose ();
+      }
+      return plist;
     }
 
-
-    public static MDatabase Find (MDatabaseTag tag)
+    public object Load (MSymbol key, MSymbol stop)
     {
-      MDatabase db;
+      FileStream stream = get_stream ();
+
+      if (stream == null)
+       return null;
+      if (Info.Format == Mxml)
+       {
+         XmlDocument doc = new XmlDocument (name_table);
+         XmlTextReader reader = new XmlTextReader (stream, name_table);
+
+         reader.WhitespaceHandling = WhitespaceHandling.None;
+         try {
+           reader.Read ();
+           while (reader.NodeType != XmlNodeType.Element)
+             reader.Read ();
+           doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
+           reader.Read ();
+           XmlNode node = doc.DocumentElement;
+           while (reader.NodeType == XmlNodeType.Element
+                  ? reader.Name != stop.Name
+                  : reader.NodeType != XmlNodeType.EndElement)
+             if (reader.NodeType == XmlNodeType.Element
+                 && reader.Name == key.Name)
+               node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
+                                                       node);
+         } finally {
+           reader.Close ();
+           stream.Dispose ();
+         }
+         return doc;
+       }
 
-      return (DBDict.TryGetValue (tag, out db) ? db : null);
+      MPlist plist = null;
+      try {
+       plist = new MPlist (stream, key, stop);
+       LastLoaded = DateTime.Now;
+      } catch {
+       LastLoadStatus = LoadStatus.InvalidContents;
+      } finally {
+       stream.Dispose ();
+      }
+      return plist;
     }
 
-    public object Load ()
+    public object Load (MSymbol stop)
     {
-      return (Loader != null ? Loader (Tag, ExtraInfo)
-             : load (MSymbol.nil, MSymbol.nil));
+      FileStream stream = get_stream ();
+
+      if (stream == null)
+       return null;
+      if (Info.Format == Mxml)
+       {
+         XmlDocument doc = new XmlDocument (name_table);
+         XmlTextReader reader = new XmlTextReader (stream, name_table);
+
+         reader.WhitespaceHandling = WhitespaceHandling.None;
+         try {
+           reader.Read ();
+           while (reader.NodeType != XmlNodeType.Element)
+             reader.Read ();
+           doc.LoadXml ("<" + reader.Name + "></" + reader.Name + ">");
+           reader.Read ();
+           XmlNode node = null;
+           while (reader.NodeType == XmlNodeType.Element
+                  ? reader.Name != stop.Name
+                  : reader.NodeType != XmlNodeType.EndElement)
+             if (reader.NodeType == XmlNodeType.Element)
+                 node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader),
+                                                         node);
+         } catch (Exception e) {
+           Console.WriteLine (e);
+         } finally {
+           reader.Close ();
+           stream.Dispose ();
+         }
+         return doc;
+       }
+
+      MPlist plist = null;
+      try {
+       plist = new MPlist (stream, stop);
+       LastLoaded = DateTime.Now;
+      } catch (Exception e) {
+       Console.WriteLine (e);
+       LastLoadStatus = LoadStatus.InvalidContents;
+      } finally {
+       stream.Dispose ();
+      }
+      return plist;
     }
 
-    public object Load (MSymbol key, MSymbol stop)
+    public XmlNode Load (string id, params string[] nodes)
     {
-      if (Loader != null)
+      FileStream stream = get_stream ();
+      if (stream == null)
        return null;
-      return load (key, stop);
+      if (Info.Format != Mxml)
+       throw new Exception ("Not an XML format");
+
+      XmlDocument doc = new XmlDocument (name_table);
+      XmlTextReader reader = new XmlTextReader (stream, name_table);
+      int len = nodes.Length;
+
+      reader.WhitespaceHandling = WhitespaceHandling.None;
+      do {
+       reader.Read ();
+      } while (reader.NodeType != XmlNodeType.Element);
+
+      if (reader.Name != nodes[0])
+       return null;
+
+      string ns = reader.GetAttribute ("xmlns");
+      XmlNode top = doc.CreateNode (XmlNodeType.Element, nodes[0], ns);
+      XmlNode node = top;
+
+      try {
+       int i;
+
+       for (i = 1; i + 1 < len; i++)
+         {
+           if (! reader.ReadToDescendant (nodes[i]))
+             return null;
+           node = node.InsertAfter (doc.CreateNode (XmlNodeType.Element,
+                                                    nodes[i], ns), null);
+         }
+       if (! reader.ReadToDescendant (nodes[i]))
+         return null;
+       XmlNode ref_node = null;
+       while (reader.NodeType != XmlNodeType.EndElement)
+         {
+           if (reader.NodeType == XmlNodeType.Element)
+             {
+               if (reader.Name == nodes[i]
+                   && (id == null || id == reader.GetAttribute ("id")))
+                 ref_node = node.InsertAfter (doc.ReadNode (reader), ref_node);
+               else
+                 reader.Skip ();
+             }
+           else
+             reader.Read ();
+         }
+             
+      } catch (Exception e) {
+       Console.WriteLine (e);
+      } finally {
+       reader.Close ();
+       stream.Dispose ();
+      }
+      return top;
     }
 
-    private object load (MSymbol key, MSymbol stop)
+    /// <summary>Return a list of currently available database
+    /// directory names</summary>.
+    public static string[] DirectoryList ()
     {
-      LoadedTime = DateTime.UtcNow;
+      List<string> dirs = new List<string> ();
 
-      
+      for (int i = 1; i < 4; i++)
+       if (DBDirs[i].Dirname != null)
+         dirs.Add (DBDirs[i].Dirname);
+      return dirs.ToArray ();
+    }
 
-      return null;
+    public MSymbol Format { get { return Info.Format; } }
+
+    public static bool Changed (DateTime time)
+    {
+      update_all (false);
+      return (time < LastUpdateTime);
     }
 
-  }
+    public bool NeedReload ()
+    {
+      return update_status ();
+    }  
 
-}
\ No newline at end of file
+    // For IComparable<MDatabase>
+    public int CompareTo (MDatabase other)
+    {
+      return (ListIndex == other.ListIndex
+             ? DirIndex - other.DirIndex
+             : ListIndex - other.ListIndex);
+    }
+  }
+}