StreamDaemon.java
/*
** Module : StreamDaemon.java
** Public : low level stream primitives in a form that is suitable for use remotely
**
** Copyright (c) 2006-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
** 001 GES 20060211 @24555 Created initial version which provides low level stream primitives
** in a form that is suitable for use remotely.
** 002 GES 20060212 @24565 Added external lookup of stream by id.
** 003 GES 20060215 @24677 Special close hooks for redirected streams and writeByte().
** 004 GES 20060226 @24755 Added methods to remote the paging state.
** 005 GES 20060817 @28659 Redirected cleanup processing to the client.
** 006 GES 20061102 @30905 During cleanup processing, the stream may extend its last page,
** causing page footers to emit. If this is the case AND the terminal
** is not currently redirected, then we force a temporary redirection.
** This may be the case if the explicit stream forms of terminal
** redirection are in use (as opposed to the implicit terminal
** redirection that occurs when the unnamed stream is not the
** terminal).
** 007 NVS 20070724 @34679 Now this class maintains the reverse lookup map for stream IDs by
** stream as well. The corresponding method has been added.
** 008 NVS 20070725 @34691 Implemented methods isIn() and isOut(), which are newly added to the
** LowLevelStream. Fixed resource leaks: streams map needed removal of
** the closed streams.
** 009 NVS 20070731 @34757 Implemented advancePage() method, which is newly added to the
** LowLevelStream.
** 010 NVS 20070802 @34777 Implemented flush() method, which is newly added to the
** LowLevelStream.
** 011 NVS 20070906 @34973 Correction to an error message.
** 012 SIY 20070928 @35327 Added support for necessary methods. Ref: #35314
** 013 SIY 20070928 @35408 Fix for delimiter handling regression. Ref: #35404
** 014 SIY 20071017 @35491 Added setControl() method. Ref: #35486
** 015 CA 20071228 @36617 Added method putWorkerUnformatted to be used put statement with
** unformatted values.
** 016 CA 20080304 @37295 Added method setUnnamed.
** 017 SIY 20080317 @37454 Added support for terminal stream. Ref: #37453
** 018 GES 20080412 @38022 Added method setConvert().
** 019 GES 20080425 @38124 Refactored to eliminate round trips to the client by passing more
** data in a single invocation. This improves performance.
** 020 SVL 20090612 @42672 Added setUnbuffered() method. If a StopCondition has been raised
** into closeOut() or close(), we let the stream to be closed in a
** usual way. The "Attempt to write to closed stream" error has been
** added to the putWorker().
** 021 GES 20090624 @42894 Eliminate certain placeholder error processing when the stream is
** closed. More correct processing is now done on the server side.
** 022 SIY 20090925 @44031 Added getNextLineNum() method.
** 023 SVL 20110403 Added write(byte[]) function.
** 024 GES 20110812 Added readChunk() method.
** 025 GES 20111025 Modified API export code to capture and use the
** authentication token so that multiple registrations
** can be done per JVM session. This occurs when the
** client code is running in the server process and
** there is a CTRL-C that causes the client to
** restart.
** 026 GES 20111111 Modified interface export to allow registration
** to be exclusively local or remote as needed.
** 027 CS 20130103 Implement OS-DIR support as an INPUT stream
** 028 VIG 20130921 Added support of printer stream functionality
** (for OUTPUT TO PRINTER statement)
** 029 CA 20140730 Protected stream close/cleanup code from raised conditions, to allow
** close/cleanup fully complete even if conditions were raised.
** 030 CA 20140805 ThinClient is explicitly imported, to isolate ChUI class usage
** outside of ChUI packages.
** 031 GES 20141030 Added setConvertSource/Target() methods.
** 032 EVK 20141012 Added explicit terminal stream support.
** Updated openFileStream to open terminal stream if filename is "term"
** or "terminal".
** 033 OM 20170613 Added setBinary() method. Added peekCh() method.
** 034 HC 20171024 Runtime support for SYSTEM-DIALOG PRINT-SETUP and OUTPUT TO PRINTER.
** 035 ECF 20171026 Added write(byte[], int, int) method.
** 036 ECF 20180110 Normalize file separators when opening a stream.
** 038 SBI 20180213 Implemented openClipboardStream().
** 039 SBI 20180518 Added PathResolver usages in order to resolve a legacy file system
** path on the host system.
** 040 SBI 20180529 Changed openFileStream and openDirStream in order to resolve case
** insensitive path names.
** 041 HC 20190712 Implemented stream-by-name resolution.
** 042 GES 20190714 Code cleanup.
** HC 20190716 Reworked the stream-by-name resolution, this involved reverting the
** stream-by-name changes done in entry 041.
** 043 RFB 20200420 Added setKeepMessages method for support of the KEEP-MESSAGES option.
** Updated javadoc.
** 044 CA 20210317 Improved performance of the readBlock(memptr) API.
** CA 20210709 Added APIs to read pristine bytes.
** CA 20220515 memptr and library calls are supported on server-side, too. Added TODO's for
** client-side APIs which will need to call back to server-side, when memptr is used
** as argument.
** TJD 20220504 Java 11 compatibility minor changes
** EVL 20221212 Adding LOB-DIR runtime support.
** 045 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.
** 046 GBB 20231213 Adding flag hasDuplicatedLastLine.
** 047 GBB 20240826 Moving OSResourceManager to osresource package.
** 048 GBB 20240912 Moving file / dir stream methods to FileSystem.
** Dependency on ThinClient replaced by the new interface Terminal.
** 049 FER 20250127 Addd 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 java.io.*;
import java.util.*;
import com.goldencode.p2j.ui.chui.*;
import com.goldencode.p2j.util.osresource.*;
/**
* Provides a network interface for stream creation and stream operations which are remote.
*/
public class StreamDaemon
implements StreamHelper
{
/** Represents the terminal. */
private Terminal terminal = null;
/** Stores the list of active streams, keyed by integral stream id. */
private Map<Integer, Stream> streams = new HashMap<Integer, Stream>();
/** Same list but for reverse lookups of ID by stream */
private Map<Stream, Integer> ids = new HashMap<Stream, Integer>();
/** Next id to attempt to allocate. */
private int next = 0;
/**
* Create an instance and export its API to the network.
*
* @param terminal
* Provides access to terminal features and helpers.
*/
public StreamDaemon(Terminal terminal)
{
this.terminal = terminal;
}
/**
* Constructs a remote stream instance representing a process' standard I/O.
*
* @return The stream id which is used to route operations to the newly created instance.
*/
public int openProcessStream()
{
return store(new ProcessStream());
}
/**
* Constructs a remote stream instance representing the terminal.
*
* @return The stream id which is used to route operations to the newly created instance.
*/
public int openTerminalStream()
{
return store(new TerminalStream(terminal));
}
/**
* 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 int openNullStream()
{
return store(new NullStream());
}
/**
* Constructs the output stream into the client's system clipboard.
*
* @return The stream id which is used to route operations to the newly created instance.
*/
@Override
public int openClipboardStream()
{
return store(new ClipboardStream());
}
/**
* Constructs a remote printer stream instance. This can be assigned as unnamed output
* (OUTPUT TO PRINTER statement) or as a named stream (OUTPUT STREAM someStream TO PRINTER).
*
* @param name
* The name of the target printer. May be <code>null</code>.
* @param options
* Print options.
*
* @return The stream id which is used to route operations to the newly created instance.
*/
public int openPrinterStream(String name, PrintOptions options)
{
if (this.terminal.isChui())
{
return ChUIPrinterStreamSupport.openPrinterStream(name, this);
}
else
{
return GUIPrinterStreamSupport.openPrinterStream(name, this, options);
}
}
/**
* The number of bytes available to be immediately read without blocking.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The number of available bytes.
*
* @throws IOException
*/
public long available(int id)
throws IOException
{
Stream s = lookup(id, -1, "check available bytes from");
if (s == null)
{
return -1;
}
return s.available();
}
/**
* State of the input side of the stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return <code>true</code> if the input side of the stream is active.
*/
public boolean isIn(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.isIn();
}
return false;
}
/**
* State of the output side of the stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return <code>true</code> if the output side of the stream is active.
*/
public boolean isOut(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.isOut();
}
return false;
}
/**
* Inserts a page break if the stream is paged and if the next write
* position is not on the first line of a new page.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @throws ErrorConditionException
*/
public void advancePage(int id)
throws ErrorConditionException
{
Stream s = streams.get(id);
if (s == null)
{
return;
}
s.advancePage();
}
/**
* Flushes the stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void flush(int id)
{
Stream s = streams.get(id);
if (s == null)
{
return;
}
s.flush();
}
/**
* The 0-based offset into the stream at which the next read or write will
* occur. This does not work for "streams" that require sequential
* access.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The current position in the stream.
*
* @throws IOException
* @throws UnsupportedOperationException
*/
public long getPos(int id)
throws UnsupportedOperationException,
IOException
{
Stream s = streams.get(id);
if (s == null)
{
return -1;
}
return s.getPos();
}
/**
* Moves the current read/write position to the specified absolute 0-based
* offset.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param pos
* The new read/write position in the stream.
*
* @throws IOException
* @throws UnsupportedOperationException
*/
public void setPos(int id, long pos)
throws UnsupportedOperationException,
IOException
{
Stream s = streams.get(id);
if (s != null)
{
s.setPos(pos);
}
}
/**
* The length of the stream in bytes. This does not work for "streams"
* that require sequential access.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The length of the stream.
*
* @throws IOException
* @throws UnsupportedOperationException
*/
public long getLen(int id)
throws UnsupportedOperationException,
IOException
{
Stream s = lookup(id, -1, "get length from");
if (s == null)
{
return -1;
}
return s.getLen();
}
/**
* Gets the current page's next logical line number.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The next line number.
*/
public int getNextLineNum(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.getNextLineNum().intValue();
}
return -1;
}
/**
* Truncates or extends the stream to the specified length if this stream
* supports such an operation. If truncation is requested, all data
* located after this point in the file is discarded. If extending the
* file is requested, the values of the data in the extended portion of
* the file is undefined.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param len
* The new length of the file.
*
* @throws IOException
* @throws UnsupportedOperationException
*/
public void setLen(int id, long len)
throws UnsupportedOperationException,
IOException
{
Stream s = lookup(id, -1, "set length on");
if (s != null)
{
s.setLen(len);
}
}
/**
* Write the given character to the output stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param ch
* The character to be written.
*
* @throws IOException
*/
public void writeCh(int id, char ch)
throws IOException
{
Stream s = lookup(id, 1387, "write to");
if (s != null)
{
s.writeCh(ch);
}
}
/**
* Write the given byte to the output stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param b
* The byte to be written.
*
* @throws IOException
*/
public void writeByte(int id, byte b)
throws IOException
{
Stream s = lookup(id, 1387, "write to");
if (s != null)
{
s.writeByte(b);
}
}
/**
* Write the given string to the output stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param data
* The data to be written.
*
* @throws IOException
*/
public void write(int id, String data)
throws IOException
{
Stream s = lookup(id, 1387, "write to");
if (s != null)
{
s.write(data);
}
}
/**
* Write the given byte array to the output stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param data
* The data to be written.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(int id, byte[] data)
throws IOException
{
write(id, data, 0, data.length);
}
/**
* Write the specified range of bytes from the given byte array to the output stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param data
* The data to be written.
* @param offset
* Starting offset in data from which to read bytes to be written. Must be
* non-negative and {@code < data.length}.
* @param length
* Length of data to be written. Must be non-negative and {@code <= (data.length
* - offset)}.
*
* @throws IOException
* If an I/O error occurs.
*/
public void write(int id, byte[] data, int offset, int length)
throws IOException
{
Stream s = lookup(id, 1387, "write to");
if (s != null)
{
s.write(data, offset, length);
}
}
/**
* Peeks at the character from the current read position in the stream (reads a character from
* the current read position in the stream without incrementing stream read position. The next
* {@code peekCh()} and {@code readCh()} will return the same value).
* <p>
* The underlying stream subclass determines the content of the result. Byte oriented streams
* such as pipes or files will return a byte while streams that generate keystrokes or
* characters may return a DBCS or Unicode character.
*
* @return The next character read from the stream, -1 on any failure and -2 upon an
* {@code EOF}.
*/
public int peekCh(int id)
{
Stream s = lookup(id, 1386, "read from");
return (s == null) ? -1 : s.peekCh();
}
/**
* Read a character from the current read position in the stream.
* <p>
* The underlying stream subclass determines the content of the result.
* Byte oriented streams such as pipes or files will return a byte while
* streams that generate keystrokes or characters may return a DBCS or
* Unicode character.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The next character read from the stream, -1 on any failure
* and -2 upon an <code>EOF</code>.
*/
public int readCh(int id)
{
Stream s = lookup(id, 1386, "read from");
if (s == null)
{
return -1;
}
return s.readCh();
}
/**
* Read a single byte from the underlying stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return A single byte from the stream.
*/
public int readByte(int id)
{
Stream s = lookup(id, 1386, "read from");
if (s == null)
{
return -1;
}
return s.readByte();
}
/**
* Read a block of data from the stream with the block size equal to the size of the binary data array in
* the given <code>memptr</code> variable. The data will be written into the memptr starting at the first
* byte in the array. The read data will be the smaller of the block size or the file size. Only an error
* or end of file condition will cause this read to terminate before the block size has been read.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param var
* The variable to which the result is assigned.
*
* @return The number of the bytes read, -1 on any failure and -2 upon an <code>EOF</code>.
*/
@Override
public int readBlock(int id, memptr var)
{
// TODO: memptr may be on server-side, improve this
Stream s = lookup(id, 1386, "read from");
if (s == null)
{
return -1;
}
return s.readBlockWorker(var);
}
/**
* Read all characters from the current read position in the stream to the
* next line separator (as determined by the <code>File.separator</code>
* or to the <code>EOF</code>. Any line separator character(s) and the
* <code>EOF</code> character are not returned.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The next line read from the stream.
*
* @throws EOFException
* @throws IOException
* @throws InterruptedException
*/
public String readLn(int id)
throws EOFException,
IOException,
InterruptedException
{
Stream s = lookup(id, 1386, "read from");
if (s == null)
{
return null;
}
return s.readLn();
}
/**
* Read a chunk of characters from the current read position in the stream to the
* offset calculated by given length or to the <code>EOF</code>, whichever
* comes first. The <code>EOF</code> character is not returned.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param len
* The number of bytes to read.
*
* @return The chunk read from the stream or <code>null</code> if there
* is nothing to read (the <code>EOF</code> has been reached). The
* length of the returned array will be the smaller of the
* <code>len</code> parameter or the actual bytes left before the
* <code>EOF</code>.
*
* @throws IOException
* If an I/O error occurs.
*/
public byte[] readChunk(int id, int len)
throws IOException
{
Stream s = lookup(id, 1386, "read from");
if (s == null)
{
return null;
}
return s.readChunk(len);
}
/**
* Read a chunk of bytes from the current read position in the stream to the
* offset calculated by given length or to the <code>EOF</code>, whichever
* comes first. The <code>EOF</code> character is not returned.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param len
* The number of bytes to read.
*
* @return The chunk read from the stream or <code>null</code> if there
* is nothing to read (the <code>EOF</code> has been reached). The
* length of the returned array will be the smaller of the
* <code>len</code> parameter or the actual bytes left before the
* <code>EOF</code>.
*
* @throws IOException
* If an I/O error occurs.
*/
public byte[] readBytes(int id, int len)
throws IOException
{
Stream s = lookup(id, 1386, "read from");
if (s == null)
{
return null;
}
return s.readBytes(len);
}
/**
* Closes the input stream and releases OS resources associated with it.
* Any output stream that is open is unaffected.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void closeIn(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.closeIn();
if (!s.isOut())
{
cleanupMaps(s, id);
}
}
}
/**
* Closes the output stream and releases OS resources associated with it.
* Any input stream that is open is unaffected.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void closeOut(int id)
{
Stream s = streams.get(id);
StopConditionException exc = null;
if (s != null)
{
try
{
s.closeOut();
}
catch(StopConditionException e)
{
exc = e;
}
terminal.cleanupPseudoTerminal(id);
if (!s.isIn())
{
cleanupMaps(s, id);
}
}
if (exc != null)
throw exc;
}
/**
* Closes the stream and releases OS resources associated with it. After
* completion, further calls may not be made to use this instance, until
* an <code>assign</code> is used to "re-open" the stream. This is safe to
* call when no stream is open (it will do nothing in that case).
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void close(int id)
{
Stream s = streams.get(id);
StopConditionException exc = null;
if (s != null)
{
try
{
s.close();
}
catch(StopConditionException e)
{
exc = e;
}
terminal.cleanupPseudoTerminal(id);
cleanupMaps(s, id);
}
if (exc != null)
throw exc;
}
/**
* Detects when a page break is needed.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return <code>true</code> if a page break should be output.
*/
public boolean needsPageBreak(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.needsPageBreak();
}
return false;
}
/**
* Gets the current page size in lines.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The number of lines per page.
*/
public int rawGetPageSize(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.rawGetPageSize();
}
return -1;
}
/**
* Sets the current page size in lines.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param sz
* The number of lines per page.
*/
public void rawSetPageSize(int id, int sz)
{
Stream s = streams.get(id);
if (s != null)
{
s.rawSetPageSize(sz);
}
}
/**
* Gets the current page number.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The current page number.
*/
public int rawGetPageNum(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.rawGetPageNum();
}
return -1;
}
/**
* Increments the current page number.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void rawIncrementPageNum(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.rawIncrementPageNum();
}
}
/**
* Gets the current page's next line number.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The next line number.
*/
public int rawGetNextLineNum(int id)
{
Stream s = streams.get(id);
if (s != null)
{
return s.rawGetNextLineNum();
}
return -1;
}
/**
* Increments the current page's next line number.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void rawIncrementNextLineNum(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.rawIncrementNextLineNum();
}
}
/**
* Resets the current page's next line number to 1.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void rawResetNextLineNum(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.rawResetNextLineNum();
}
}
/**
* Perform common close <b>preprocessing</b> for all streams.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param in
* <code>true</code> if this was triggered during a call to
* {@link #closeIn} or {@link #close}. Input resources are
* to be cleaned up.
* @param out
* <code>true</code> if this was triggered during a call to
* {@link #closeOut} or {@link #close}. Output resources are
* to be cleaned up.
*
* @throws IOException
*/
public void processCleanup(int id, boolean in, boolean out)
throws IOException
{
Stream s = streams.get(id);
if (s != null)
{
boolean remove = false;
if (s.hasPageHeadersFooters() && !terminal.isRedirected())
{
remove = true;
terminal.switchOutput(id, false);
}
try
{
s.cleanup(in, out);
}
finally
{
if (remove)
{
terminal.switchOutput(-1, false);
}
}
}
}
/**
* Returns the stream associated with a given id.
*
* @param id
* Specifies the stream on which this operation is to execute.
*
* @return The stream or <code>null</code> if no stream is associated
* with the given id.
*/
public Stream streamFromId(int id)
{
return streams.get(id);
}
/**
* Returns the stream ID associated with a given stream.
*
* @param s
* stream to operate on
*
* @return The stream ID or <code>-1</code> if no stream ID is associated
* with the given stream.
*/
public int idFromStream(Stream s)
{
return reverseLookup(s, -1, "access a");
}
/**
* Write the current output buffer to the stream. This flushes all
* buffered output of the {@link Stream#putField}, {@link Stream#putSpace},
* {@link Stream#putLineEnd} and {@link Stream#writeField} methods to the
* stream. This method also resets the current delimiter to the default.
* <p>
* This method does nothing if the stream is in <code>control</code> mode.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param appendNewLine
* Automatically add new line at the end of output.
*
* @throws ErrorConditionException
* On any error during I/O.
* @throws StopConditionException
* If any interruption occurs during I/O processing.
*/
public void endOutput(int id, boolean appendNewLine)
throws ErrorConditionException,
StopConditionException
{
Stream s = streams.get(id);
if (s != null)
{
s.endOutput(appendNewLine);
}
}
/**
* Output a list of fields to the stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param data
* The list of fields to be output.
* @param mode
* <code>MODE_PUT_NORMAL</code>, <code>MODE_PUT_CONTROL</code>,
* <code>MODE_PUT_UNFORMATTED</code> or <code>MODE_EXPORT</code>.
* @param delim
* The delimiter to use in EXPORT mode.
*
* @throws ErrorConditionException
* On any error during I/O.
*/
public void putWorker(int id, FieldEntry[] data, int mode, char delim)
throws ErrorConditionException
{
Stream s = streams.get(id);
if (s != null)
{
s.putWorker(data, mode, delim);
}
else
{
ErrorManager.recordOrThrowError(1387, "Attempt to write to closed stream");
}
}
/**
* Set the unnamed field for the stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param unnamed
* <code>true</code> if it is an unnamed stream.
*/
public void setUnnamed(int id, boolean unnamed)
{
Stream s = streams.get(id);
if (s != null)
{
s.setUnnamed(unnamed);
}
}
/**
* Set the the stream UNBUFFERED.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
public void setUnbuffered(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.setUnbuffered();
}
}
/**
* Sets the stream's character conversion mode.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param convert
* The new character conversion mode. <code>true</code> to
* enable character conversion, <code>false</code> to disable
* conversion.
*/
public void setConvert(int id, boolean convert)
{
Stream s = streams.get(id);
if (s != null)
{
s.setConvert(convert);
}
}
/**
* Sets the stream's source codepage and activates character conversion mode.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param cp
* The source codepage for character conversion mode.
*/
public void setConvertSource(int id, String cp)
{
Stream s = streams.get(id);
if (s != null)
{
s.setConvertSource(cp);
}
}
/**
* Sets the stream's target codepage and activates character conversion mode.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param cp
* The target codepage for character conversion mode.
*/
public void setConvertTarget(int id, String cp)
{
Stream s = streams.get(id);
if (s != null)
{
s.setConvertTarget(cp);
}
}
/**
* Sets the stream's lob directory located as a subdirectory for opened current file stream directory.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param lobDir
* The name for subdirectory to store LOB files.
*/
@Override
public void setLobDirectory(int id, String lobDir)
{
Stream s = streams.get(id);
if (s != null)
{
s.setLobDirectory(lobDir);
}
}
/**
* Sets the stream into binary mode which disables character translations during I/O
* operations. It is important to note that field and line reading (<code>import</code>,
* <code>set</code>, <code>update</code> <code>prompt-for</code> and <code>import
* unformatted</code>) are unaffected by this setting (this is based on Progress testcases).
*/
@Override
public void setBinary(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.setBinary();
}
}
/**
* Sets the stream font number. The number denotes an index into the font table
* of the current environment.
* The method is part of the runtime support of the FONT option of the OUTPUT TO PRINTER
* (FWD extension to 4GL) statement. This method is only implemented by the printer stream.
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param num
* Font number.
*/
@Override
public void setFont(int id, int num)
{
Stream s = streams.get(id);
if (s != null)
{
s.setFont(num);
}
}
/**
* Set stream display mode to landscape
*
* @param id Specifies the stream on which this operation is to execute.
*/
@Override
public void setLandscape(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.setLandscape();
}
}
/**
* Set stream display mode to portrait
*
* @param id Specifies the stream on which this operation is to execute.
*/
@Override
public void setPortrait(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.setPortrait();
}
}
/**
* Sets the stream's <code>keepMessages</code> mode which causes certain UI statements that write
* data to the default window to be suppressed when outpu is being redirected.
* The types of UI statements are 4GL errors and warnings, and UI statements written
* using MESSAGE.
*
* @param id
* Specifies the stream on which this operation is to execute.
*/
@Override
public void setKeepMessages(int id)
{
Stream s = streams.get(id);
if (s != null)
{
s.setKeepMessages();
}
}
/**
* Assign a new stream id and store the given stream reference using the
* id as a key.
*
* @param s
* The stream to store.
*
* @return The associated id or -1 if the stream instance is
* <code>null</code>.
*/
public int store(Stream s)
{
int result = -1;
if (s != null)
{
// assumes that the map doesn't have 2 billion keys otherwise we
// will get an infinite loop here
while (result == -1)
{
Integer id = Integer.valueOf(next);
if (!streams.containsKey(id))
{
streams.put(id, s);
ids.put(s, id);
result = next;
}
next++;
// wrap to 0 instead of to negative numbers, 2 billion ids per
// client should be enough
if (next == Integer.MAX_VALUE)
{
next = 0;
}
}
}
return result;
}
/**
* Accesses the stream in the map using the given id. An error condition
* will be raised if the id is invalid (no stream exists by that id).
*
* @param id
* Specifies the stream on which this operation is to execute.
* @param num
* Error number to use on failure.
* @param msg
* Error text to insert into a standardized message starting
* with "Attempt to" and ending with "closed stream ###" where
* the ### represents the stream's id.
*
* @return The local stream reference to use for all operations.
*/
private Stream lookup(int id, int num, String msg)
{
Stream s = streams.get(id);
if (s == null)
{
String errmsg = "Attempt to " + msg + " closed stream #" + id;
ErrorManager.recordOrThrowError(num, errmsg);
// nothing safe to return in silent mode!
}
return s;
}
/**
* Accesses the stream ID in the map using the given stream.
*
* @param s
* Specifies the stream on which this operation is to execute.
* @param num
* Error number to use on failure.
* @param msg
* Error text to insert into a standardized message starting
* with "Attempt to" and ending with "closed stream".
*
* @return The local stream ID
*/
private int reverseLookup(Stream s, int num, String msg)
{
Integer id = ids.get(s);
if (id == null)
{
String errmsg = "Attempt to " + msg + " closed stream";
ErrorManager.recordOrThrowError(num, errmsg);
return -1;
}
return id.intValue();
}
/**
* Removes the traces of the specified stream from the lookup maps.
*
* @param s
* Specifies the stream on which this operation is to execute.
* @param id
* Specified the stream's ID.
*/
private void cleanupMaps(Stream s, int id)
{
streams.remove(Integer.valueOf(id));
ids.remove(s);
}
}