m3tk/src/files/Common/M3DirFindFile.m3


 Copyright (C) 1993, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              

MODULE M3DirFindFile;

IMPORT
  CITextRefTbl, TextRefTbl, Rd, RdExtras, FileRd, RefList, ASCII,
  M3Directory, M3Extension, M3FindFile, M3PathElem, M3PathElemList,
  Pathname, OSError;

IMPORT Thread;
<*FATAL M3FindFile.Failed, Rd.Failure, Thread.Alerted*>

REVEAL Finder = FinderPublic BRANDED OBJECT
    table: TextRefTbl.T;
    allDirs: M3PathElemList.T;
    extSet: M3Extension.TSet;
    extCount: CARDINAL;
    extToIndex: ARRAY M3Extension.T OF CARDINAL;
    indexToExt: IndexToExts;
  METHODS
    addDir(dir: M3PathElem.T) RAISES {OSError.E} := AddDir;
  OVERRIDES
    exts := Exts;
    init := Init;
    find := Find;
    dirOf := DirOf;
    dirs := Dirs;
    iterate := NewIter;
    setProperty := SetProperty;
    getProperty := GetProperty;
    merge := Merge;
    openRead := OpenRead;
  END;

REVEAL
  TFinder = TFinderPublic BRANDED OBJECT
    fileLocs: RefList.T; (* OF FileLoc *)
    rd: Rd.T; (* passed to "init" *)
  OVERRIDES
    init := TInit;
    addDir := TAddDir;
  END;

TYPE
  TFileLoc = REF RECORD index: CARDINAL; elem: M3PathElem.T END;

TYPE
  IndexToExts = ARRAY [ORD(FIRST(M3Extension.T))..ORD(LAST(M3Extension.T))]
    OF M3Extension.T;

  (* data associated with each unit entered in the hash table *)
  Info = REF ARRAY OF RECORD
    dir: M3PathElem.T;  (* handle on associated directory, NIL => missing *)
    userData: REFANY;   (* place to hang other stuff, e.g. filestamp *)
  END;

PROCEDURE BuildHashTable(dirs: M3PathElemList.T;
    oldFinder: Finder;
    newFinder: Finder;
    errorHandler: ErrorHandler): Finder RAISES {OSError.E}=
  VAR
    d: M3PathElem.T;
  BEGIN
    InitHashTable(newFinder);
    newFinder.allDirs := dirs;
    (* Any directory which is read-only in 'dirs' AND appears
       in the old finder list, can have its info copied into
       the new hash table.
    *)
    WHILE dirs # NIL DO
      d := dirs.head;
      IF oldFinder # NIL AND d.readOnly() AND
         AppearsIn(oldFinder.allDirs, d) THEN
        VAR
	  iter := oldFinder.table.iterate();
	  name: TEXT;
	  val: REFANY;
	  info: Info;
	BEGIN
	  WHILE iter.next(name, val) DO
	    info := NARROW(val, Info);
	    FOR i := 0 TO oldFinder.extCount-1 DO
	      IF info[i].dir # NIL AND
	          info[i].dir = d THEN
	        Add(newFinder, name, oldFinder.indexToExt[i], d);
		SetProperty(newFinder, name, oldFinder.indexToExt[i],
		    info[i].userData);
              END;
	    END; (* for *)
	  END; (* while *)
	END;
      ELSE
        (* needs scanning *)
        IF errorHandler = NIL THEN newFinder.addDir(d);
        ELSE
          TRY
            newFinder.addDir(d);
           EXCEPT OSError.E(ec) =>
             IF NOT errorHandler.callback(d, ec) THEN
               RETURN NIL
             END;
           END;
        END;
      END;
      dirs := dirs.tail;
    END; (* while *)
    RETURN newFinder;
  END BuildHashTable;

PROCEDURE AppearsIn(dirs: M3PathElemList.T; d: M3PathElem.T): BOOLEAN=
  BEGIN
    WHILE dirs # NIL DO
      IF dirs.head = d THEN RETURN TRUE END;
      dirs := dirs.tail;
    END; (* while *)
    RETURN FALSE;
  END AppearsIn;

PROCEDURE InitHashTable(f: Finder) RAISES {} =
  BEGIN
    IF (*Directory.CaseSensitive()*) TRUE THEN
      f.table := NEW(TextRefTbl.Default).init();
    ELSE f.table := NEW(CITextRefTbl.Default).init();
    END;
  END InitHashTable;

PROCEDURE AddDir(f: Finder; d: M3PathElem.T) RAISES {OSError.E} =
  VAR
    i := M3Directory.NewIter(d.text(), f.extSet);
    name: TEXT;
    ext: M3Extension.T;
  BEGIN
    WHILE i.next(name, ext) DO
      Add(f, name, ext, d);
    END; (* while *)
    i.close();
  END AddDir;

EXCEPTION TAddDirFailed;

PROCEDURE TAddDir(f: TFinder; d: M3PathElem.T) RAISES {} =
  VAR l := f.fileLocs;
      fileLoc: TFileLoc;
  BEGIN
    WHILE l # NIL DO
      fileLoc := NARROW(l.head, TFileLoc);
      IF fileLoc.elem = d THEN
        EXIT;
      ELSE
        l := l.tail;
      END
    END;
    IF l=NIL THEN <*FATAL TAddDirFailed*> BEGIN RAISE TAddDirFailed END;END;

    VAR name: TEXT; ext: M3Extension.T;
    BEGIN
      Rd.Seek(f.rd, fileLoc.index);
      LOOP
        TRY
          IF RdExtras.Skip(f.rd) = '@' THEN EXIT END;
          name := RdExtras.GetText(f.rd);
          IF M3Extension.Has(name, ext) AND ext IN f.extSet THEN
            Add(f, Pathname.Base(name), ext, d);
          END;
        EXCEPT
        | Rd.EndOfFile => EXIT
        END;
      END;
    END;
  END TAddDir;

PROCEDURE TInit(
    newFinder: TFinder;
    exts: M3Extension.TSet;
    rd: Rd.T;
    oldFinder: Finder := NIL)
    : Finder
    RAISES {OSError.E} =
  VAR
    refList: RefList.T := NIL;
    dirs: M3PathElemList.T := NIL;
  BEGIN
    LOOP
      TRY
        IF RdExtras.Skip(rd, ASCII.Spaces, unget := FALSE) = '@' THEN
          VAR
            dirName := RdExtras.GetText(rd, unget := FALSE);
            index := Rd.Index(rd);
            elem := M3PathElem.FromText(dirName, dirName, TRUE);
          BEGIN
            refList := RefList.AppendD(refList, RefList.List1(
                           NEW(TFileLoc, index := index, elem := elem)));
            dirs := M3PathElemList.AppendD(dirs, M3PathElemList.List1(elem));
          END;
        END;
      EXCEPT
      | Rd.EndOfFile => EXIT
      END;
    END;
    newFinder.fileLocs := refList; newFinder.rd := rd;
    RETURN Init(newFinder, exts, dirs, oldFinder);
  END TInit;

PROCEDURE Init(
    newFinder: Finder;
    exts: M3Extension.TSet;
    dirs: M3PathElemList.T;
    oldFinder: Finder := NIL;
    errorHandler: ErrorHandler := NIL)
    : Finder
    RAISES {OSError.E} =
  BEGIN
    BasicInit(newFinder, exts);
    RETURN BuildHashTable(dirs, oldFinder, newFinder, errorHandler);
  END Init;

PROCEDURE BasicInit(
    newFinder: Finder;
    exts: M3Extension.TSet)=
  VAR
    extToIndex: ARRAY M3Extension.T OF CARDINAL;
    indexToExt: IndexToExts;
    count: CARDINAL := 0;
  BEGIN
    CountAndExtToIndex(exts, count, extToIndex, indexToExt);

    newFinder.extSet := exts;
    newFinder.extCount := count; newFinder.extToIndex := extToIndex;
    newFinder.indexToExt := indexToExt;
  END BasicInit;

PROCEDURE CountAndExtToIndex(
    exts: M3Extension.TSet;
    VAR count: CARDINAL;
    VAR extToIndex: ARRAY M3Extension.T OF CARDINAL;
    VAR indexToExt: IndexToExts) RAISES {} =
  BEGIN
    count := 0;
    FOR i := FIRST(M3Extension.T) TO LAST(M3Extension.T) DO
      IF i IN exts THEN
        extToIndex[i] := count;
	indexToExt[count] := i;
        INC(count);
      END;
    END;
  END CountAndExtToIndex;

EXCEPTION Fatal;

PROCEDURE Exts(p: Finder): M3Extension.TSet RAISES {}=
  BEGIN
    RETURN p.extSet;
  END Exts;

PROCEDURE Find(
    m: Finder;
    name: TEXT;
    ext: M3Extension.T)
    : TEXT
    RAISES {M3FindFile.Failed}=
  BEGIN
    RETURN Pathname.Join(DirOf(m, name, ext).text(),
                            name, M3Extension.ToText(ext))
  END Find;

PROCEDURE DirOf(
    m: Finder;
    name: TEXT;
    ext: M3Extension.T): M3PathElem.T RAISES {M3FindFile.Failed}=
  VAR
    id: REFANY;
  BEGIN
    IF NOT ext IN m.extSet THEN <*FATAL Fatal*> BEGIN RAISE Fatal END; END;
    IF m.table.get(name, id) THEN
      VAR
        p := NARROW(id, Info);
	dir := p[m.extToIndex[ext]].dir;
      BEGIN
        IF dir # NIL THEN RETURN dir END;
      END;
    END; (* if *)
    RAISE M3FindFile.Failed;
  END DirOf;

PROCEDURE Add(
    m: Finder;
    name: TEXT;
    ext: M3Extension.T;
    dir: M3PathElem.T)
    RAISES {}=
  VAR
    id: REFANY;
    index := m.extToIndex[ext];
    info: Info;
  BEGIN
    IF m.table.get(name, id) THEN
      info := NARROW(id, Info);
      IF info[index].dir # NIL THEN
      	RETURN (* duplicate, later in path, ignored *)
      END; (* if *)
    ELSE
      info := NEW(Info, m.extCount);
      FOR i := 0 TO m.extCount-1 DO
        WITH xinfo = info[i] DO
      	  xinfo.dir := NIL; xinfo.userData := NIL;
        END;
      END; (* for *)
      EVAL m.table.put(name, info);
    END; (* if *)
    info[index].dir := dir;
  END Add;

PROCEDURE NewIter(f: Finder): Iter RAISES {} =
  BEGIN
    RETURN NEW(Iter,
               hashIter := f.table.iterate(),
               f := f);
  END NewIter;

PROCEDURE Next(
    i: Iter;
    VAR (*out*) unitName: TEXT;
    VAR (*out*) ext: M3Extension.T;
    VAR (*out*) dir: M3PathElem.T)
    : BOOLEAN
    RAISES {} =
  VAR
    si: CARDINAL;
  BEGIN
    LOOP
      IF i.info = NIL THEN
        VAR
          val: REFANY;
        BEGIN
          IF NOT i.hashIter.next(unitName, val) THEN
	    RETURN FALSE;
	  END;
          i.info := NARROW(val, Info);
        END;
      END;
      (* got a unit, iterate the extensions *)
      dir := i.info[i.i].dir; si := i.i;
      INC(i.i);
      IF i.i >= i.f.extCount THEN i.i := 0; i.info := NIL; END;
      IF dir # NIL THEN
	ext := i.f.indexToExt[si];
	RETURN TRUE;
      END;
    END; (* loop *)
  END Next;

PROCEDURE Dirs(f: Finder): M3PathElemList.T RAISES {}=
  BEGIN
    RETURN f.allDirs;
  END Dirs;

REVEAL Iter = IterPublic BRANDED OBJECT
    hashIter: CITextRefTbl.Iterator;
    f: Finder;
    i: CARDINAL := 0;
    info: Info := NIL;
  OVERRIDES
    next := Next;
    close := Close;
  END;

PROCEDURE Close(<*UNUSED*> iter: Iter)=
  BEGIN
  END Close;

PROCEDURE SetProperty(
    f: Finder;
    unitName: TEXT;
    ext: M3Extension.T;
    value: REFANY)
    RAISES {M3FindFile.Failed}=
  VAR
    id: REFANY;
  BEGIN
    IF NOT ext IN f.extSet THEN <*FATAL Fatal*> BEGIN RAISE Fatal END; END;
    IF f.table.get(unitName, id) THEN
      VAR
        p := NARROW(id, Info);
	dir := p[f.extToIndex[ext]].dir;
      BEGIN
        IF dir # NIL THEN
	  p[f.extToIndex[ext]].userData := value;
        END;
      END;
    ELSE RAISE M3FindFile.Failed
    END;
  END SetProperty;

PROCEDURE GetProperty(
    f: Finder;
    unitName: TEXT;
    ext: M3Extension.T)
    : REFANY RAISES {M3FindFile.Failed}=
  VAR
    id: REFANY;
  BEGIN
    IF NOT ext IN f.extSet THEN <*FATAL Fatal*> BEGIN RAISE Fatal END; END;
    IF f.table.get(unitName, id) THEN
      VAR
        p := NARROW(id, Info);
	dir := p[f.extToIndex[ext]].dir;
      BEGIN
        IF dir # NIL THEN
	  RETURN p[f.extToIndex[ext]].userData;
        END;
      END;
    END (* IF *);
    RAISE M3FindFile.Failed
  END GetProperty;

PROCEDURE OpenRead(f: Finder; name: TEXT; ext: M3Extension.T
      ): Rd.T RAISES {OSError.E, M3FindFile.Failed}=
  BEGIN
    RETURN FileRd.Open(f.find(name, ext));
  END OpenRead;

PROCEDURE Merge(self, f1, f2: Finder): Finder=
  VAR
    exts := f1.extSet;
  BEGIN
    IF f2 # NIL THEN exts := exts + f2.extSet END;
    BasicInit(self, exts);
    InitHashTable(self);
    MergeOne(f1, self);
    IF f2 # NIL THEN MergeOne(f2, self); END;
    RETURN self;
  END Merge;

PROCEDURE MergeOne(from, to: Finder)=
  VAR
    iter := from.table.iterate();
    name: TEXT;
    val: REFANY;
    info: Info;
    from_dirs := from.allDirs;
  BEGIN
    WHILE from_dirs # NIL DO
      IF NOT M3PathElemList.Member(to.allDirs, from_dirs.head) THEN
        to.allDirs := M3PathElemList.AppendD(to.allDirs,
            M3PathElemList.List1(from_dirs.head));
      END;
      from_dirs := from_dirs.tail;
    END;
    WHILE iter.next(name, val) DO
      info := NARROW(val, Info);
      FOR i := 0 TO from.extCount-1 DO
        IF info[i].dir # NIL THEN
          Add(to, name, from.indexToExt[i], info[i].dir);
          SetProperty(to, name, from.indexToExt[i],
		      info[i].userData);
        END;
      END;
    END;
  END MergeOne;

BEGIN
END M3DirFindFile.