UNSAFE MODULE M3Path EXPORTS M3Path; (***************************************************************************) (* Copyright (C) Olivetti 1989 *) (* All Rights reserved *) (* *) (* Use and copy of this software and preparation of derivative works based *) (* upon this software are permitted to any person, provided this same *) (* copyright notice and the following Olivetti warranty disclaimer are *) (* included in any copy of the software or any modification thereof or *) (* derivative work therefrom made by any person. *) (* *) (* This software is made available AS IS and Olivetti disclaims all *) (* warranties with respect to this software, whether expressed or implied *) (* under any law, including all implied warranties of merchantibility and *) (* fitness for any purpose. In no event shall Olivetti be liable for any *) (* damages whatsoever resulting from loss of use, data or profits or *) (* otherwise arising out of or in connection with the use or performance *) (* of this software. *) (***************************************************************************) IMPORT HashText, M3Directory, M3Extension, M3FindFile, PathName, IO, SList, Text; CONST AllExts = M3Extension.TSet{FIRST(M3Extension.T)..LAST(M3Extension.T)}; REVEAL Finder = FinderPublic BRANDED OBJECT END; TYPE PathFind = Finder OBJECT extSet: M3Extension.TSet; table: HashText.Table; localDir: Text.T := ""; allDirs: SList.T; METHODS add(name: Text.T; ext: M3Extension.T; dir: Elem); OVERRIDES exts := Exts; END; IndexToExts = ARRAY [ORD(FIRST(M3Extension.T))..ORD(LAST(M3Extension.T))] OF M3Extension.T; MultipleExtsPathFind = PathFind OBJECT extCount: CARDINAL; extToIndex: ARRAY M3Extension.T OF CARDINAL; indexToExt: IndexToExts; OVERRIDES find := MultipleFind; add := MultipleAdd; findDir := MultipleFindDir; END; (* data associated with each unit entered in the hash table *) Info = REF ARRAY OF RECORD dir: Elem; (* handle on associated directory, NIL => missing *) userData: REFANY; (* place to hang other stuff, e.g. filestamp *) END; TYPE FinderElem = SList.Elem OBJECT exts := AllExts; dir := ""; name := FileName; doTransitiveClosure := TRUE; finder: Finder; END; VAR allFinders_g := SList.T{}; PROCEDURE BuildHashTable(dirs: SList.T; oldFinderElem: FinderElem; newFinder: MultipleExtsPathFind)RAISES {}= VAR d: Elem := dirs.head; oldFinder: MultipleExtsPathFind := NIL; BEGIN InitHashTable(newFinder); newFinder.allDirs := dirs; IF oldFinderElem # NIL THEN oldFinder := oldFinderElem.finder; SList.Remove(allFinders_g, oldFinderElem); END; (* 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 d # NIL DO IF oldFinder # NIL AND d.readOnly AND AppearsIn(oldFinder.allDirs, d) THEN VAR iter := HashText.NewIterator(oldFinder.table); name: TEXT; val: REFANY; info: Info; BEGIN WHILE HashText.Next(iter, name, val) DO info := NARROW(val, Info); FOR i := 0 TO oldFinder.extCount-1 DO IF info[i].dir # NIL AND Same(info[i].dir, d) THEN newFinder.add(name, oldFinder.indexToExt[i], d); SetProperty(newFinder, name, oldFinder.indexToExt[i], info[i].userData); END; END; (* for *) END; (* while *) END; ELSE (* needs scanning *) AddOneDir(d, newFinder); END; d := d.next; END; (* while *) END BuildHashTable; PROCEDURE AppearsIn(dirs: SList.T; d: Elem): BOOLEAN RAISES {}= VAR dir: Elem := dirs.head; BEGIN WHILE dir # NIL DO IF Same(dir, d) THEN RETURN TRUE END; dir := dir.next; END; (* while *) RETURN FALSE; END AppearsIn; (*PRIVATE*) PROCEDURE InitHashTable(f: PathFind) RAISES {} = BEGIN IF PathName.CaseSensitive() THEN f.table := HashText.New(); ELSE f.table := HashText.CINew(); END; END InitHashTable; (*PRIVATE*) PROCEDURE AddOneDir(d: Elem; f: PathFind) RAISES {} = VAR i := M3Directory.New(d.text, f.extSet); name: Text.T; ext: M3Extension.T; BEGIN WHILE M3Directory.Next(i, name, ext) DO f.add(name, ext, d); END; (* while *) END AddOneDir; (*PUBLIC*) PROCEDURE Find( exts: M3Extension.TSet; dir := ""; name := FileName; doTransitiveClosure := TRUE; forceRead := FALSE) : Finder RAISES {IO.Error, BadDirName} = BEGIN RETURN FindFinder(exts, dir, name, doTransitiveClosure, forceRead); END Find; (*PRIVATE*) PROCEDURE FindFinder( exts: M3Extension.TSet; dir := ""; name := FileName; doTransitiveClosure := TRUE; forceRead := FALSE) : Finder RAISES {IO.Error, BadDirName} = VAR finderElem := FinderOnList(exts, dir, name, doTransitiveClosure); result: PathFind; BEGIN IF NOT forceRead THEN IF finderElem # NIL THEN RETURN finderElem.finder; END; ELSE (* this means that we have already scanned the directories once, so we can ignore read-only directories this time! Rather we can just replicate the contents from last time. *) END; (* have to build one by steam... *) VAR extToIndex: ARRAY M3Extension.T OF CARDINAL; indexToExt: IndexToExts; count: CARDINAL := 0; BEGIN CountAndExtToIndex(exts, count, extToIndex, indexToExt); result := NEW(MultipleExtsPathFind, extSet := exts, localDir := dir, extCount := count, extToIndex := extToIndex, indexToExt := indexToExt); BuildHashTable(Read(dir, name, doTransitiveClosure), finderElem, result); SList.AddRear( allFinders_g, NEW(FinderElem, exts := exts, dir := dir, name := name, doTransitiveClosure := doTransitiveClosure, finder := result)); RETURN result; END; (* var dirs, etc. *) END FindFinder; (*PRIVATE*) 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; (*PRIVATE*) PROCEDURE FinderOnList( exts := AllExts; dir := ""; name := FileName; doTransitiveClosure := TRUE) : FinderElem RAISES {} = VAR finder: FinderElem := allFinders_g.head; BEGIN WHILE finder # NIL DO IF (finder.exts = exts) AND Text.Equal(finder.dir, dir) AND Text.Equal(finder.name, name) AND (finder.doTransitiveClosure = doTransitiveClosure) THEN RETURN finder; (* found one built *) END; finder := finder.next; END; RETURN NIL; END FinderOnList; EXCEPTION Fatal; PROCEDURE Exts(p: PathFind): M3Extension.TSet RAISES {}= BEGIN RETURN p.extSet; END Exts; PROCEDURE MultipleFind( m: MultipleExtsPathFind; name: Text.T; ext: M3Extension.T) : Text.T RAISES {M3FindFile.Failed}= BEGIN RETURN PathName.Full(MultipleFindDir(m, name, ext).text, M3Extension.Extend(name, ext)) END MultipleFind; PROCEDURE MultipleFindDir( m: MultipleExtsPathFind; name: Text.T; ext: M3Extension.T): Elem RAISES {M3FindFile.Failed}= VAR id: HashText.Id; BEGIN IF NOT ext IN m.extSet THEN RAISE Fatal END; IF HashText.Lookup(m.table, name, id) THEN VAR p := NARROW(HashText.Value(m.table, id), Info); dir := p[m.extToIndex[ext]].dir; BEGIN IF dir # NIL THEN RETURN dir END; END; END; (* if *) RAISE M3FindFile.Failed; END MultipleFindDir; PROCEDURE MultipleAdd( m: MultipleExtsPathFind; name: Text.T; ext: M3Extension.T; dir: Elem) RAISES {}= VAR id: HashText.Id; index := m.extToIndex[ext]; info: Info; BEGIN IF HashText.Enter(m.table, name, id) THEN 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 *) HashText.Associate(m.table, id, info); ELSE info := HashText.Value(m.table, id); IF info[index].dir # NIL THEN RETURN (* duplicate, later in path, ignored *) END; (* if *) END; (* if *) info[index].dir := dir; END MultipleAdd; TYPE MultipleIter = Iter; (*PRIVATE*) PROCEDURE MultipleNewIter(f: MultipleExtsPathFind): MultipleIter RAISES {} = BEGIN RETURN NEW(MultipleIter, hashIter := HashText.NewIterator(f.table), f := f); END MultipleNewIter; (*PRIVATE*) PROCEDURE MultipleNext( i: MultipleIter; VAR (*out*) unitName: TEXT; VAR (*out*) ext: M3Extension.T; VAR (*out*) dir: Elem) : BOOLEAN RAISES {} = VAR si: CARDINAL; BEGIN LOOP IF i.info = NIL THEN VAR val: REFANY; BEGIN IF NOT HashText.Next(i.hashIter, 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 MultipleNext; PROCEDURE FinderDirs(f: Finder): SList.T RAISES {}= BEGIN RETURN NARROW(f, PathFind).allDirs; END FinderDirs; REVEAL Iter = BRANDED REF RECORD hashIter: HashText.Iter; f: MultipleExtsPathFind; i: CARDINAL := 0; info: Info := NIL; END; (*PUBLIC*) PROCEDURE NewIter(f: Finder): Iter RAISES {}= BEGIN RETURN MultipleNewIter(f); END NewIter; (*PUBLIC*) PROCEDURE Next( iter: Iter; VAR (*out*) unitName: TEXT; VAR (*out*) ext: M3Extension.T; VAR (*out*) dir: Elem) : BOOLEAN RAISES {}= BEGIN RETURN MultipleNext(iter, unitName, ext, dir); END Next; PROCEDURE SetProperty( f: Finder; unitName: TEXT; ext: M3Extension.T; value: REFANY) RAISES {}= VAR id: HashText.Id; m: MultipleExtsPathFind := f; BEGIN IF NOT ext IN m.extSet THEN RAISE Fatal END; IF HashText.Lookup(m.table, unitName, id) THEN VAR p := NARROW(HashText.Value(m.table, id), Info); dir := p[m.extToIndex[ext]].dir; BEGIN IF dir # NIL THEN p[m.extToIndex[ext]].userData := value; END; END; END; END SetProperty; PROCEDURE GetProperty( f: Finder; unitName: TEXT; ext: M3Extension.T) : REFANY RAISES {}= VAR id: HashText.Id; m: MultipleExtsPathFind := f; BEGIN IF NOT ext IN m.extSet THEN RAISE Fatal END; IF HashText.Lookup(m.table, unitName, id) THEN VAR p := NARROW(HashText.Value(m.table, id), Info); dir := p[m.extToIndex[ext]].dir; BEGIN IF dir # NIL THEN RETURN p[m.extToIndex[ext]].userData; END; END; END; END GetProperty; BEGIN END M3Path.