MODULE SplitStream; (***************************************************************************) (* 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 Text; IMPORT IO, IO_impl; TYPE T = IO.Stream OBJECT arr: REF Array := NIL; errant: IO.Stream := NIL; OVERRIDES implFlush := Flush; implDescribeError := Error; END; PROCEDURE Flush(s: T; READONLY chars: ARRAY OF CHAR): BOOLEAN RAISES {}= (* Put 'chars' to each sub stream in turn. If a put fails exit the loop, save the errant stream and return FALSE *) BEGIN TRY FOR i := 0 TO LAST(s.arr^) DO WITH sub = s.arr[i] DO LOCK sub DO IO.PutN(sub, chars); IO.Flush(sub) END; END; END; RETURN TRUE; EXCEPT | IO.Error(errant) => s.errant := errant; RETURN FALSE; END; END Flush; PROCEDURE Error(s: T): Text.T RAISES {}= (* Either an open failure or sub stream failure *) BEGIN IF IO.WhyErrant(s) = IO.Fault.Open THEN RETURN "attempt to open split stream to unusable or readonly sub stream"; ELSE LOCK s.errant DO VAR fault := IO.WhyErrant(s.errant); BEGIN IF fault # IO.Fault.None THEN VAR result := "sub stream error - " & IO.FaultToText(fault); describeText := IO.DescribeError(s.errant); BEGIN IF describeText = NIL THEN RETURN result; ELSE RETURN result & ": " & describeText; END; END; ELSE (* Very unlikely - someone's cleared up the sub stream error *) RETURN "flush to sub stream failed but sub stream no longer errant"; END; END; END; END; END Error; <*INLINE*> PROCEDURE Copy(READONLY a: Array): REF Array RAISES {}= VAR copy := NEW(REF Array, NUMBER(a)); BEGIN copy^ := a; RETURN copy; END Copy; EXCEPTION ReadonlyStream; PROCEDURE Open( READONLY streams: Array; unbuffered := FALSE; name := "split") : IO.Stream RAISES {IO.Error}= VAR new := NEW(T, arr := Copy(streams)); buffer: REF ARRAY OF CHAR := NIL; properties := IO.PropertySet{}; BEGIN (* Look through 'streams', searching for the following: 1) Find the smallest sub stream buffer size; this will be the size we'll use, unless "unbuffered" is TRUE. 2) If any sub stream is unbuffered or line buffered we must be too. 3) If any sub stream is readonly we must force an error 4) If any sub stream is unusable we must force an error *) TRY VAR smallest := LAST(CARDINAL); BEGIN IF unbuffered THEN smallest := 1 END; FOR i := 0 TO LAST(new.arr^) DO VAR sub := new.arr[i]; subProperties: IO.PropertySet; BEGIN LOCK sub DO subProperties := IO.Properties(sub); (* Note that 'BufferSize' will raise 'IO.Error' if 'sub' is not usable *) smallest := MIN(smallest, IO_impl.BufferSize(sub)); END; IF NOT IO.Property.Writable IN subProperties THEN RAISE ReadonlyStream; END; IF IO.Property.LineBuffered IN subProperties THEN properties := properties + IO.PropertySet{IO.Property.LineBuffered}; END; END; END; buffer := NEW(REF ARRAY OF CHAR, smallest); END; EXCEPT | IO.Error, ReadonlyStream => (* Unusable or readonly sub stream; leave 'buffer' at NIL to force 'IO_impl.Init' to raise 'IO.Error' *) END; (* Initialize the stream *) IO_impl.Init( new, buffer, IO.UnknownLength, IO.OpenMode.Write, properties, name); RETURN new; END Open; BEGIN END SplitStream.