# StreamFactory.java

    /*
    ** Module : StreamFactory.java
    ** Public : creates stream instances on a remote node
    **
    ** Copyright (c) 2006-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 GES 20060211   @24554 Created initial version which creates file and process stream
    **                           instances on a remote node.
    ** 002 SVL 20080219   @37121 openFileStream() returns null if filename equals "terminal".
    ** 003 SIY 20080317   @37456 Added support for terminal stream. Ref: #37453
    ** 004 SIY 20090331   @41707 Added more openFileStream() signatures and added detection of
    **                           interactive terminal redirection. Ref: #41705
    ** 005 SIY 20090401   @41714 Fixed regression introduced by #004: wrong order of page size setting calls.
    ** 006 SIY 20090401   @41715 Do not call setPageSiz() if NO_PAGING is passed as a page size and
    **                           paging flag is set to false. Yet another regression from #004.
    ** 007 GES 20111020          Modified per-session initialization to detect when the client code
    **                           is running inside the server process AND to initialize for local
    **                           redirection instead of using a remote object.
    ** 008 CS  20130103          Implement OS-DIR support as an INPUT stream
    ** 009 VIG 20120918          Added printer stream support for OUTPUT TO PRINTER statement.
    ** 010 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 011 EVK 20140601          Empty directory name should be current directory "."
    **                           Added additional methods for openFileStream.
    **                           Added explicit support for the terminal stream.
    ** 012 CA  20170829          Added openClipboardStream - not yet supported, returns NullStream.
    ** 013 HC  20171024          Runtime support for SYSTEM-DIALOG PRINT-SETUP and OUTPUT TO PRINTER.
    ** 014 GES 20180115          Added a signature for CLIPBOARD construction.
    ** 015 SBI 20180213          Linked Clipboard output streams with their implementations.
    ** 016 SBI 20180412          Fixed javadoc warnings.
    ** 017 HC  20190714          Implemented stream-by-name resolution.
    ** 018 OM  20210830          Added CachedInputStream and universal support for reading input streams from jar.
    **     CA  20220428          Added openWebStream stubs, for WebSpeed.
    ** 019 HC  20230427          Implemented server-side file-system resource. File operations
    **                           performed on client can be optionally performed directly on server
    **                           without the expensive remote call.
    ** 020 GBB 20231213          Adding flag hasDuplicatedLastLine.
    ** 021 GBB 20240826          Moving OSResourceManager to osresource package.
    ** 022 GBB 20240912          File & dir streams to be created as RemoteStream.
    ** 023 FER 20250127          Added openNullStream() method.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.util.osresource.*;

    import java.io.*;

    /**
     * Creates file and process stream instances that are on the remote node associated with the current user's
     * session and which are accessed via a {@link RemoteStream} instance.
     */
    public class StreamFactory
    {
       /** Special page size which causes turning paging off.*/
       private static final int NO_PAGING = -2;

       /** Stores context-local state variables. */
       private static final ContextContainer work = new ContextContainer();

       /**
        * Constructs a remote stream instance representing a file using a filename, this file or device will be
        * opened for reading or writing based on the given {@code write} flag. The current read/write position
        * will be at offset 0 unless in {@code write} mode and the {@code append} flag is on (in which case the
        * write-position will be at the end of the file).
        *
        * @param    filename
        *           The name of the file or device to open.  Must not be {@code null}.
        * @param    write
        *           If {@code true}, open in write mode, otherwise open the file for reading.
        * @param    append
        *           In {@code write} mode, if {@code true}, the current file pointer is set to the end of the
        *           file. Otherwise, if in {@code write} mode, the file is truncated to 0 length. This value is
        *           ignored in {@code read} mode.
        * @param    pageSz
        *           Desired page size. {@code -1} means default paging.
        * @param    paged
        *           {@code true} means that stream is paged.
        *
        * @return   The new stream instance or {@code null} if an error occurred while silent error mode was
        * active.
        *
        * @throws   ErrorConditionException
        *           If the file does not exist OR if the current user does not have the proper rights to open the
        *           file or device.
        */
       public static Stream openFileStream(String  filename,
                                           boolean write,
                                           boolean append,
                                           int     pageSz,
                                           boolean paged)
       {
          return openFileStream(false, filename, write, append, pageSz, paged);
       }

       /**
        * Constructs a remote stream instance representing a file using a filename, this file or device will be
        * opened for reading or writing based on the given {@code write} flag. The current read/write position
        * will be at offset 0 unless in {@code write} mode and the {@code append} flag is on (in which case the
        * write-position will be at the end of the file).
        *
        * @param    hasDuplicatedLastLine
        *           Flag to indicate if the last line in the file is duplicate (true for LOB files).
        * @param    filename
        *           The name of the file or device to open.  Must not be {@code null}.
        * @param    write
        *           If {@code true}, open in write mode, otherwise open the file for reading.
        * @param    append
        *           In {@code write} mode, if {@code true}, the current file pointer is set to the end of the
        *           file. Otherwise, if in {@code write} mode, the file is truncated to 0 length. This value is
        *           ignored in {@code read} mode.
        * @param    pageSz
        *           Desired page size. {@code -1} means default paging.
        * @param    paged
        *           {@code true} means that stream is paged.
        *
        * @return   The new stream instance or {@code null} if an error occurred while silent error mode was
        * active.
        *
        * @throws   ErrorConditionException
        *           If the file does not exist OR if the current user does not have the proper rights to open the
        *           file or device.
        */
       public static Stream openFileStream(boolean hasDuplicatedLastLine,
                                           String  filename,
                                           boolean write,
                                           boolean append,
                                           int     pageSz,
                                           boolean paged)
       throws ErrorConditionException
       {
          boolean terminal = false;
          if (filename != null)
          {
             String term = filename.toLowerCase();

             if (term.equals("terminal") || term.equals("term"))
             {
                filename = "terminal";
                terminal = true;
             }
          }

          // terminal stream without paging is not allowed
          if (terminal && !paged && pageSz == NO_PAGING)
          {
             return openTerminalStream();
          }

          if (!write)
          {
             // firstly attempt to locate the resource inside application's jar:
             InputStream stream = LogicalTerminal.getResourceStreamFromApplication(filename);
             if (stream != null)
             {
                // make sure these kinds of streams support stream mark/reset operations needed for some
                // operations in FWD. To work around this, we will cache the whole content in a ByteArray
                try
                {
                   return new CachedInputStream(stream);
                }
                catch (IOException e)
                {
                   throw new ErrorConditionException(e.getMessage());
                }
             }
          }

          int id = work.obtain().fileSystem.openFileStream(filename, write, append, hasDuplicatedLastLine);
          Stream res = (id < 0) ? null : new RemoteStream(id, terminal);

          if (res != null)
          {
             if ((pageSz < 0 && pageSz != NO_PAGING) || paged)
             {
                res.setPageSize();
             }

             if (pageSz >= 0)
             {
                res.setPageSize(pageSz);
             }
          }

          return res;
       }

       /**
        * Constructs a remote stream instance representing a file using a filename, this file or
        * device will be opened for reading or writing based on the given <code>write</code> flag.
        * The current read/write position will be at offset 0 unless in <code>write</code> mode and
        * the <code>append</code> flag is on (in which case the write position will be at the end of
        * the file).
        *
        * @param    filename
        *           The name of the file or device to open.  Must not be <code>null</code>.
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        * @param    sz
        *           Desired page size. <code>-1</code> means default paging.
        *
        * @return   The new stream instance or <code>null</code> if an error occurred while silent
        *           error mode was active.
        *
        * @throws   ErrorConditionException
        *           If the file does not exist OR if the current user does not have the proper rights
        *           to open the file or device.
        */
       public static Stream openFileStream(String filename, boolean write, boolean append, int sz)
       {
          return openFileStream(filename, write, append, sz, false);
       }

       /**
        * Constructs a remote stream instance representing a file using a filename, this file or
        * device will be opened for reading or writing based on the given <code>write</code> flag.
        * The current read/write position will be at offset 0 unless in <code>write</code> mode and
        * the <code>append</code> flag is on (in which case the write position will be at the end of
        * the file).
        * <p>
        * This stream will NOT have paging active.
        *
        * @param    hasDuplicatedLastLine
        *           Flag to indicate if the last line in the file is duplicate (true for LOB files).
        * @param    filename
        *           The name of the file or device to open.  Must not be <code>null</code>.
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        *
        * @return   The new stream instance or <code>null</code> if an error occurred while silent
        *           error mode was active.
        *
        * @throws   ErrorConditionException
        *           If the file does not exist OR if the current user does not have the proper rights
        *           to open the file or device.
        */
       public static Stream openFileStream(boolean hasDuplicatedLastLine,
                                           String filename,
                                           boolean write,
                                           boolean append)
       {
          return openFileStream(hasDuplicatedLastLine, filename, write, append, NO_PAGING, false);
       }

       /**
        * Constructs a remote stream instance representing a file using a filename, this file or
        * device will be opened for reading or writing based on the given <code>write</code> flag.
        * The current read/write position will be at offset 0 unless in <code>write</code> mode and
        * the <code>append</code> flag is on (in which case the write position will be at the end of
        * the file).
        * <p>
        * This stream will NOT have paging active.
        *
        * @param    filename
        *           The name of the file or device to open.  Must not be <code>null</code>.
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        *
        * @return   The new stream instance or <code>null</code> if an error occurred while silent
        *           error mode was active.
        *
        * @throws   ErrorConditionException
        *           If the file does not exist OR if the current user does not have the proper rights
        *           to open the file or device.
        */
       public static Stream openFileStream(String filename, boolean write, boolean append)
       {
          return openFileStream(filename, write, append, NO_PAGING, false);
       }

       /**
        * Constructs a remote stream instance representing a file using a filename, this file or
        * device will be opened for reading or writing based on the given <code>write</code> flag.
        * The current read/write position will be at offset 0 unless in <code>write</code> mode and
        * the <code>append</code> flag is on (in which case the write position will be at the end of
        * the file).
        *
        * @param    filename
        *           The name of the file or device to open.  Must not be <code>null</code>.
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        * @param    paged
        *           <code>true</code> means that stream is paged with the default page size.
        *
        * @return   The new stream instance or <code>null</code> if an error occurred while silent
        *           error mode was active.
        *
        * @throws   ErrorConditionException
        *           If the file does not exist OR if the current user does not have the proper rights
        *           to open the file or device.
        */
       public static Stream openFileStream(String  filename,
                                           boolean write,
                                           boolean append,
                                           boolean paged)
       {
          return openFileStream(filename, write, append, NO_PAGING, paged);
       }

       /**
        * Constructs a remote stream instance representing a stream result of
        * INPUT FROM OS-DIR statement.
        *
        * @param  dir
        *         The <code>String</code> variable representing the directory for
        *         which OS-DIR is applied.
        * @param  noAttrList
        *         This indicates if type index will no be output to stream If
        *         <code>true</code> the index will not be output to the stream,
        *         if <code>false</code> it will be output.
        *
        * @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
        *         containing the result of OS-DIR implementation, or null if
        *         an error has occurred.
        */
       public static Stream openDirStream(String dir, boolean noAttrList)
       {
          String fixDir = "".equals(dir) ? "." : dir;
          return new RemoteStream(work.obtain().fileSystem.openDirStream(fixDir, noAttrList));
       }

       /**
        * Constructs a remote stream instance representing a stream result of
        * INPUT FROM OS-DIR statement.
        * <p>
        * When no boolean variable representing the noAttrList which decides if
        * file type will be added to the stream is present the file type will be
        * added by default.
        *
        * @param  dir
        *         The <code>String</code> variable representing the directory for
        *         which OS-DIR is applied.
        *
        * @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
        *         containing the result of OS-DIR implementation, or null if
        *         an error has occurred.
        */
       public static Stream openDirStream(String dir)
       {
          return openDirStream(dir, false);
       }

       /**
        * Constructs a remote stream instance representing a stream result of
        * INPUT FROM OS-DIR statement.
        *
        * @param  dir
        *         The <code>character</code> variable representing the directory
        *         for which OS-DIR is applied.
        * @param  noAttrList
        *         This indicates if type index will no be output to stream If
        *         <code>true</code> the index will not be output to the stream,
        *         if <code>false</code> it will be output.
        *
        * @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
        *         containing the result of OS-DIR implementation, or null if
        *         an error has occurred.
        */
       public static Stream openDirStream(character dir, boolean noAttrList)
       {
          return openDirStream(dir.toStringMessage(), noAttrList);
       }

       /**
        * Constructs a remote stream instance representing a stream result of
        * INPUT FROM OS-DIR statement.
        * <p>
        * When no boolean variable representing the noAttrList which decides if
        * file type will be added to the stream is present the file type will be
        * added by default.
        *
        * @param  dir
        *         The <code>character</code> variable representing the directory
        *         for which OS-DIR is applied.
        *
        * @return The <code>Stream</code> wrapping the <code>OsDirStream</code>
        *         containing the result of OS-DIR implementation, or null if
        *         an error has occurred.
        */
       public static Stream openDirStream(character dir)
       {
          return openDirStream(dir.toStringMessage(), false);
       }

       /**
        * Constructs a remote stream instance representing a process' standard I/O.
        *
        * @return   The new stream instance.
        */
       public static Stream openProcessStream()
       {
          return new RemoteStream(work.obtain().streamBuilder.openProcessStream());
       }

       /**
        * Open a new stream as 'WEB'.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        *
        * @return   The clipboard stream
        */
       public static Stream openWebStream(boolean write)
       {
          return null;
       }

       /**
        * Open a new stream as 'WEB'.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    paged
        *           <code>true</code> if the stream is paged.
        *
        * @return   The clipboard stream
        */
       public static Stream openWebStream(boolean write, boolean paged)
       {
          UnimplementedFeature.missing("WEB streams are not supported.");
          return null;
       }

       /**
        * Open a new stream as 'WEB'.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        *
        * @return   The clipboard stream
        */
       public static Stream openWebStream(boolean write, int pageSz)
       {
          return null;
       }

       /**
        * Open a new stream as 'WEB'.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        * @param    paged
        *           <code>true</code> if the stream is paged.
        *
        * @return   The clipboard stream
        */
       public static Stream openWebStream(boolean write, int pageSz, boolean paged)
       {
          return null;
       }

       /**
        * Open a new stream targeting the OS clipboard.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        *
        * @return   The clipboard stream
        */
       public static Stream openClipboardStream(boolean write)
       {
          return openClipboardStream(write, -1, false);
       }

       /**
        * Open a new stream targeting the OS clipboard.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    paged
        *           <code>true</code> if the stream is paged.
        *
        * @return   The clipboard stream
        */
       public static Stream openClipboardStream(boolean write, boolean paged)
       {
          return openClipboardStream(write, -1, paged);
       }

       /**
        * Open a new stream targeting the OS clipboard.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        *
        * @return   The clipboard stream
        */
       public static Stream openClipboardStream(boolean write, int pageSz)
       {
          return openClipboardStream(write, pageSz, true);
       }

       /**
        * Open a new stream targeting the OS clipboard.
        *
        * @param    write
        *           If <code>true</code>, open in write mode, otherwise open the file for reading.
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        * @param    paged
        *           <code>true</code> if the stream is paged.
        *
        * @return   The clipboard stream
        */
       public static Stream openClipboardStream(boolean write, int pageSz, boolean paged)
       {
          int id = work.obtain().streamBuilder.openClipboardStream();

          if (id >= 0)
          {
             Stream remoteStream = new RemoteStream(id);
             if (paged)
             {
                if (pageSz >= 0)
                {
                   remoteStream.setPageSize(pageSz);
                }
                else
                {
                   remoteStream.setPageSize();
                }
             }
             return remoteStream;
          }

          return null;
       }

       /**
        * Constructs a remote stream instance representing the terminal.
        *
        * @return   The new stream instance.
        */
       public static Stream openTerminalStream()
       {
          return openTerminalStream(false, NO_PAGING, false);
       }

       /**
        * Constructs a remote stream instance representing the terminal.
        *
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        *
        * @return   The new stream instance.
        */
       public static Stream openTerminalStream(int pageSz)
       {
          return openTerminalStream(false, pageSz, false);
       }

       /**
        * Constructs a remote stream instance representing the terminal.
        *
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        *
        * @return   null in any case.
        */
       public static Stream openTerminalStream(boolean append)
       {
          return openTerminalStream(append, NO_PAGING, false);
       }

       /**
        * Constructs a remote stream instance representing the terminal.
        *
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        * @param    paged
        *           <code>true</code> if the stream is paged.
        *
        * @return   null in any case.
        */
       public static Stream openTerminalStream(boolean append, boolean paged)
       {
          return openTerminalStream(append, NO_PAGING, paged);
       }

       /**
        * Constructs a remote stream instance representing the terminal.
        *
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        *
        * @return   null in any case.
        */
       public static Stream openTerminalStream(boolean append, int pageSz)
       {
          return openTerminalStream(append, pageSz, false);
       }

       /**
        * Constructs a remote stream instance representing the terminal.
        *
        * @param    append
        *           In <code>write</code> mode, if <code>true</code>, the current file pointer is
        *           set to the end of the file. Otherwise if in <code>write</code> mode, the file
        *           is truncated to 0 length. This value is ignored in <code>read</code> mode.
        * @param    pageSz
        *           Desired page size. <code>-1</code> means default paging.
        * @param    paged
        *           <code>true</code> means that stream is paged.
        *
        * @return   null if stream is not paged.
        */
       public static Stream openTerminalStream(boolean append, int pageSz, boolean paged)
       {
          if (!paged && pageSz == NO_PAGING)
          {
             return null;
          }
          else
          {
             return new RemoteStream(work.obtain().streamBuilder.openTerminalStream(), true);
          }
       }

       /**
        * Constructs a remote stream instance representing the Null Stream.
        *
        * @return   The stream id which is used to route operations to the
        *           newly created instance.
        */
       public static Stream openNullStream()
       {
          return new RemoteStream(work.obtain().streamBuilder.openNullStream());
       }

       /**
        * Constructs a remote stream instance representing the printer Stream
        * result of the OUTPUT TO PRINTER statement.
        * It can be assinged as unnamed output (OUTPUT TO PRINTER statement)
        * or as named stream (OUTPUT STREAM someStream TO PRINTER)
        *
        * @return   The stream id which is used to route operations to the
        *           newly created instance.
        */
       public static Stream openPrinterStream()
       {
          return openPrinterStream(null);
       }

       /**
        * Constructs a remote stream instance representing the printer Stream
        * result of the OUTPUT TO PRINTER statement.
        * It can be assinged as unnamed output (OUTPUT TO PRINTER statement)
        * or as named stream (OUTPUT STREAM someStream TO PRINTER)
        *
        * @param    printerName
        *           name of target printer
        *
        * @return   The stream id which is used to route operations to the
        *           newly created instance.
        */
       public static Stream openPrinterStream(String printerName)
       {
          PrintingService.validatePrinterName(printerName);
          PrintOptions opts = PrintingService.getPrintOptions();
          return new RemoteStream(work.obtain().streamBuilder.openPrinterStream(printerName, opts));
       }

       /**
        * Finds an output stream by its name. Pass {@code null} or an empty string for the unnamed output stream.
        *
        * @param    name
        *           Stream name.
        *
        * @return   The remote stream instance or {@code null} if no such stream exists.
        */
       public static Stream findOutputStream(String name)
       {
          if (name == null || name.isEmpty())
          {
             return UnnamedStreams.output();
          }
          else
          {
             int id = StreamWrapper.findRemoteStreamId(name);
             return id == -1 ? null : new RemoteStream(id);
          }
       }

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /** The remote stream builder instance. */
          private StreamBuilder streamBuilder = null;

          /** The file system instance. */
          private FileSystem fileSystem = null;
       }

       /**
        * Simple container that stores and returns a context-local instance of
        * the global work area.
        */
       private static class ContextContainer
       extends ContextLocal<WorkArea>
       {
          /**
           * Obtains the context-local instance of the contained global work
           * area.
           *
           * @return   The work area associated with this context.
           */
          public WorkArea obtain()
          {
             return this.get();
          }

          /**
           * Initializes the work area, the first time it is requested within a
           * new context.
           *
           * @return   The newly instantiated work area.
           */
          protected synchronized WorkArea initialValue()
          {
             WorkArea wa = new WorkArea();

             // obtain the our proxy, server-side code is left-side by convention
             wa.streamBuilder = OSResourceManager.getStreamHelper();

             wa.fileSystem = OSResourceManager.getFileSystem();

             return wa;
          }
       }
    }