MODULE; 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 M3DirFindFile 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; PROCEDUREAppearsIn (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; PROCEDUREInitHashTable (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; PROCEDUREAddDir (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; PROCEDURETAddDir (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; PROCEDURETInit ( 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; PROCEDUREInit ( 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; PROCEDUREBasicInit ( 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; PROCEDURECountAndExtToIndex ( 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; PROCEDUREExts (p: Finder): M3Extension.TSet RAISES {}= BEGIN RETURN p.extSet; END Exts; PROCEDUREFind ( 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; PROCEDUREDirOf ( 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; PROCEDUREAdd ( 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; PROCEDURENewIter (f: Finder): Iter RAISES {} = BEGIN RETURN NEW(Iter, hashIter := f.table.iterate(), f := f); END NewIter; PROCEDURENext ( 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; PROCEDUREDirs (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; PROCEDUREClose (<*UNUSED*> iter: Iter)= BEGIN END Close; PROCEDURESetProperty ( 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; PROCEDUREGetProperty ( 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; PROCEDUREOpenRead (f: Finder; name: TEXT; ext: M3Extension.T ): Rd.T RAISES {OSError.E, M3FindFile.Failed}= BEGIN RETURN FileRd.Open(f.find(name, ext)); END OpenRead; PROCEDUREMerge (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; PROCEDUREMergeOne (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.