BufferSizeManager.java
/*
** Module : BufferSizeManager.java
** Abstract : singleton that keeps the "reference" buffer sizes and allows
** to tell when an opened stream has reached its buffer size limit
** after it has been broken.
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SVL 20090612 @42689 Created initial version. Tracks opened streams
** and in the case of error allows to tell when we
** have exceeded the buffer size limit.
** 002 SVL 20090616 @42698 Make getStreamInfo() more tolerant.
** 003 SVL 20090707 @43086 Store stream info only for ProcessStreams that
** have been broken.
** 004 GES 20111012 Moved to context-local instead of singleton. This
** allows multiple client sessions to be hosted in
** the server.
** 005 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 006 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** 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.util.logging.*;
import org.w3c.dom.*;
import java.util.*;
import java.net.URL;
import java.io.*;
import com.goldencode.p2j.directory.Base64;
import com.goldencode.util.XmlHelper;
import com.goldencode.p2j.security.*;
/**
* BufferSizeManager indended to solve the following problem: after a process
* stream fails, Progress can output some amount of data to the broken stream
* without raising any error. But after some amount of data has been written
* to this stream, data buffer is overfilled, flush is performed and STOP
* condition may be raised. This class helps us to get the proper size of data
* buffer in order to raise the STOP condition at the same point as Progress
* does.
* <p>
* BufferSizeManager acts a singleton and tracks stream opening and closing,
* blocking operations and output operations. It can tell whether it is time
* to raise the STOP condition for a given stream. It makes decision basing on
* the array of "reference" buffer sizes, which were generated from the
* results of testcases. These testcases allowed us to get buffer sizes for
* the different conditions. To know what buffer size may depend on, read
* {@link BufferSizeDependency}.
*/
public class BufferSizeManager
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(BufferSizeManager.class);
/** Context local instance of this class. */
private static final ContextLocal<BufferSizeManager> local =
new ContextLocal<BufferSizeManager>();
/** Map which stores information about opened streams. */
private final Map<ProcessStream, StreamInfo> streams =
new HashMap<ProcessStream, StreamInfo>();
/**
* Determines whether a stream (any process stream) was ever broken
* during the session.
*/
private boolean aStreamWasBroken = false;
/**
* Multidimensional array which stores "reference" buffer sizes for
* flush operations.
*/
private Object flushArray = null;
/**
* Multidimensional array which stores "reference" buffer sizes for
* close operations.
*/
private Object closeArray = null;
/**
* "type of dependency -> index of the corresponding dimension" for the
* flush operations array. E.g. UNBUFFERED -> 3 will mean that the 3rd
* dimension of the <code>flushArray</code> determines whether the stream
* is buffered or unbuffered one.
*/
private Map<BufferSizeDependency, Integer> flushParamIndexes =
new HashMap<BufferSizeDependency, Integer>();
/**
* "type of dependency -> index of the corresponding dimension" for the
* close operations array. E.g. UNBUFFERED -> 3 will mean that the 3rd
* dimension of the <code>closeArray</code> determines whether the stream
* is buffered or unbuffered one.
*/
private Map<BufferSizeDependency, Integer> closeParamIndexes =
new HashMap<BufferSizeDependency, Integer>();
/**
* "type of dependency -> mapping 'dependency value -> index inside the
* dimension'" for the flush operations array. E.g. UNBUFFERED ->
* [true -> 0, false -> 1] will mean that if we set the index inside the
* dimension which corresponds the "UNBUFFERED" dependency to 0 - we will
* get buffer size for an unbuffered stream, and if we will set it to 1 -
* we will get data for a buffered stream.
*/
private Map<BufferSizeDependency, Map<Object, Integer>> flushValuesIndexes
= new HashMap<BufferSizeDependency, Map<Object, Integer>>();
/**
* "type of dependency -> mapping 'dependency value -> index inside the
* dimension'" for the close operations array. E.g. UNBUFFERED ->
* [true -> 0, false -> 1] will mean that if we set the index inside the
* dimension which corresponds the "UNBUFFERED" dependency to 0 - we will
* get buffer size for an unbuffered stream, and if we will set it to 1 -
* we will get data for a buffered stream.
*/
private Map<BufferSizeDependency, Map<Object, Integer>> closeValuesIndexes
= new HashMap<BufferSizeDependency, Map<Object, Integer>>();
/** Determines whether "reference" buffer sizes were successfully loaded */
private boolean initialized = false;
/**
* Private c'tor which performs BufferSizeManager initialization.
*/
private BufferSizeManager()
{
try
{
URL url = ClassLoader.getSystemResource(
"com/goldencode/p2j/util/buffer_sizes.xml");
if (url == null)
throw new Exception("Cannot find buffer_sizes.xml!");
Document dom = XmlHelper.parse(url.openStream());
Element root = dom.getDocumentElement();
flushArray = readReferenceArray(root,
"flush_array_parameters",
flushParamIndexes,
flushValuesIndexes,
"flush_array_data");
closeArray = readReferenceArray(root,
"close_array_parameters",
closeParamIndexes,
closeValuesIndexes,
"close_array_data");
initialized = true;
}
catch (Exception e)
{
LOG.severe("Exception in BufferSizeManager constructor", e);
}
}
/**
* Returns singleton instance of this class.
*
* @return See above.
*/
public static synchronized BufferSizeManager getInstance()
{
BufferSizeManager instance = local.get();
if (instance == null)
{
instance = new BufferSizeManager();
local.set(instance);
}
return instance;
}
/**
* Notify that a process stream has been broken.
*
* @param stream
* The stream that was broken.
*/
public void notifyBroken(ProcessStream stream)
{
aStreamWasBroken = true;
}
/**
* Notify that an output error has occured in the process stream.
*
* @param stream
* The stream that has failed to perform output.
*/
public void notifyOutputError(ProcessStream stream)
{
streams.put(stream, new StreamInfo(stream));
}
/**
* Notify that a blocking operation has been performed.
*
* @param blockingOperation
* Blocking operation that was performed.
*/
public void notifyBlockingOperation(BlockingOperation blockingOperation)
{
for (StreamInfo streamInfo : streams.values())
{
streamInfo.notifyBlockingOperation(blockingOperation);
}
}
/**
* Notify that a stream has been closed.
*
* @param stream
* The stream that was closed.
*/
public void notifyCloseStream(ProcessStream stream)
{
streams.remove(stream);
}
/**
* Notify that an output operation has been performed.
*
* @param put
* <code>true</code> if a PUT operation was performed,
* <code>false</code> if a DISPLAY operation was performed
* @param stream
* Target stream of the output operation.
* @param bytesWritten
* Number of bytes that has been written by this operation.
*/
public void notifyOutputOperation(boolean put,
Stream stream,
int bytesWritten)
{
if (stream instanceof ProcessStream)
{
StreamInfo info = streams.get((ProcessStream) stream);
if (info != null)
{
OutputOperationType type =
getOutputDataEnumByNumber(put, bytesWritten);
info.notifyOutputOperation(type, bytesWritten);
}
}
}
/**
* Reset buffer size manager to its initial state (this does not reset
* its configuration).
*/
public void reset()
{
streams.clear();
aStreamWasBroken = false;
}
/**
* Determines whether we have exceeded the buffer size limit for a given
* stream and the STOP condition should be raised.
*
* @param close
* <code>true</code> if we are performing a close operation,
* <code>false</code> if we are performing a flush operation.
* @param stream
* The stream we are questioning for.
*
* @return <code>true</code> if we have exceeded the buffer size limit.
*/
public boolean shouldRaiseStop(boolean close, ProcessStream stream)
{
StreamInfo info = streams.get(stream);
if (info == null)
{
throw new IllegalStateException(
"Notification about stream output error is missing!");
}
return info.shouldRaiseStop(close);
}
/**
* Get the buffer size for the given stream and output operation.
*
* @param close
* <code>true</code> if we are performing a close operation,
* <code>false</code> if we are performing a flush operation.
* @param info
* Stream info object for the stream which buffer size should be
* returned.
* @param outputOperationType
* Output operation for which the buffer size should be calculated.
*
* @return See above.
*/
private int getBufferSize(boolean close,
StreamInfo info,
OutputOperationType outputOperationType)
{
Map<BufferSizeDependency, Object> map =
new HashMap<BufferSizeDependency, Object>();
map.put(BufferSizeDependency.BLOCKING_OPERATION,
info.getBlockingOperations());
map.put(BufferSizeDependency.BROKEN_STREAM, aStreamWasBroken);
map.put(BufferSizeDependency.INOUT, info.stream.isIn());
map.put(BufferSizeDependency.OUTPUT_DATA, outputOperationType);
map.put(BufferSizeDependency.OUTPUT_DESTINATION, null);
map.put(BufferSizeDependency.UNBUFFERED, info.stream.isUnbuffered());
map.put(BufferSizeDependency.UNNAMED, info.stream.isUnnamed());
if (close)
{
map.put(BufferSizeDependency.CLOSE_OPERATION, null);
}
return getBufferSize(close, map);
}
/**
* Get buffer size for the given stream state.
*
* @param close
* <code>true</code> if we are performing a close operation,
* <code>false</code> if we are performing a flush operation.
* @param parameters
* Parameters which specify the state of the stream.
*
* @return See above.
*/
private int getBufferSize(boolean close,
Map<BufferSizeDependency, Object> parameters)
{
int defaultBufferSize = getDefaultBufferSize(close, parameters);
return getBufferSize(parameters,
close ? closeArray : flushArray,
close ? closeParamIndexes : flushParamIndexes,
close ? closeValuesIndexes : flushValuesIndexes,
defaultBufferSize);
}
/**
* Get the buffer size for the given stream state from the given array of
* "reference" buffer sizes.
*
* @param parameters
* Parameters which specify the state of the stream.
* @param dataArray
* Array which contains the "reference" buffer sizes.
* @param paramMap
* Target map which will store the information about array
* dimensions. See {@link #flushParamIndexes}.
* @param valuesMap
* Target map which will store the information about indexes
* inside dimensions. See {@link #flushValuesIndexes}.
* @param defaultBufferSize
* Buffer size value to be used if we haven't found an appropriate
* value in the array of "reference" buffer sizes.
*
* @return See above.
*/
private int getBufferSize(
Map<BufferSizeDependency, Object> parameters,
Object dataArray,
Map<BufferSizeDependency, Integer> paramMap,
Map<BufferSizeDependency, Map<Object, Integer>> valuesMap,
int defaultBufferSize)
{
int size = paramMap.size();
if (size != parameters.size())
throw new
IllegalArgumentException("Invalid number of parameters in map!");
if (!initialized)
{
return defaultBufferSize;
}
int[] indexes = new int[size];
Set<BlockingOperation> blockingOberations =
(Set<BlockingOperation>)
parameters.get(BufferSizeDependency.BLOCKING_OPERATION);
if (blockingOberations == null)
{
blockingOberations = new HashSet<BlockingOperation>();
}
if (blockingOberations.isEmpty())
{
blockingOberations.add(BlockingOperation.NO_BLOCKING_OPERATION);
}
Integer min = null;
for (BlockingOperation blockingOperation : blockingOberations)
{
BufferSizeDependency dependency =
BufferSizeDependency.BLOCKING_OPERATION;
indexes[paramMap.get(dependency)] =
valuesMap.get(dependency).get(blockingOperation);
for (Map.Entry<BufferSizeDependency, Object> entry :
parameters.entrySet())
{
dependency = entry.getKey();
if (dependency.equals(BufferSizeDependency.BLOCKING_OPERATION))
continue;
Object val = entry.getValue();
indexes[paramMap.get(dependency)] =
valuesMap.get(dependency).get(val);
}
Integer res = (Integer) getElement(dataArray, indexes);
if (res != null && (min == null || min > res))
{
min = res;
}
}
if (min == null)
min = defaultBufferSize;
return min;
}
/**
* Get the default buffer size for the given set of stream parameters.
*
* @param close
* <code>true</code> if the buffer size for a close operation is
* requested, <code>false</code> if the buffer size for a flush
* operation is requested.
* @param parameters
* Parameters which describe the current state of the stream.
*
* @return See above.
*/
private int getDefaultBufferSize(
boolean close,
Map<BufferSizeDependency, Object> parameters)
{
if (close)
return (Boolean) parameters.get(BufferSizeDependency.BROKEN_STREAM) ?
512 :
512;
else
return (Boolean) parameters.get(BufferSizeDependency.BROKEN_STREAM) ?
512 :
5000;
}
/**
* Read the array of "reference" buffer size from the given XML tree.
*
* @param root
* The root node of the source XML tree.
* @param paramNode
* The name of the node which contains the array metadata.
* @param paramMap
* Target map which will store the information about array
* dimensions. See {@link #flushParamIndexes}.
* @param valuesMap
* Target map which will store the information about indexes
* inside dimensions. See {@link #flushValuesIndexes}.
* @param dataNode
* The name of the node which contains the array data.
*
* @return The array of "reference" buffer sizes.
*
* @throws IOException
* If we couldn't read the required array.
* @throws ClassNotFoundException
* If we couldn't read the required array.
*/
private Object readReferenceArray(
Element root,
String paramNode,
Map<BufferSizeDependency, Integer> paramMap,
Map<BufferSizeDependency, Map<Object, Integer>> valuesMap,
String dataNode)
throws IOException,
ClassNotFoundException
{
StringBuilder sb = new StringBuilder();
sb.append(paramNode);
sb.append("/parameters");
String path = sb.toString();
String[] dimensions = getNodeStrings(root, path);
for (int i = 0; i < dimensions.length; i++)
{
String dimension = dimensions[i];
BufferSizeDependency dependency =
BufferSizeDependency.valueOf(dimension);
paramMap.put(dependency, i);
sb = new StringBuilder();
sb.append(paramNode);
sb.append("/values");
sb.append("/");
sb.append(dimension);
path = sb.toString();
String[] values = getNodeStrings(root, path);
Map<Object, Integer> map = new HashMap<Object, Integer>();
for (int j = 0; j < values.length; j++)
{
String value = values[j];
switch (dependency)
{
case OUTPUT_DESTINATION:
case CLOSE_OPERATION:
map.put(null, j);
break;
case BROKEN_STREAM:
case UNBUFFERED:
case UNNAMED:
case INOUT:
map.put(Boolean.valueOf(value), j);
break;
case BLOCKING_OPERATION:
map.put(BlockingOperation.valueOf(value), j);
break;
case OUTPUT_DATA:
map.put(OutputOperationType.valueOf(value), j);
break;
}
}
valuesMap.put(dependency, map);
}
byte[] data = getNodeByteArray(root, dataNode);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
Object dataArray = ois.readObject();
ois.close();
bais.close();
return dataArray;
}
/**
* Get XML node by the given path.
*
* @param root
* The root node of the source XML.
* @param path
* Path to the required node (names of the nodes starting from the
* root node (exclusive), separated by "/", e.g.
* "parameters/param1/description").
*
* @return The required node.
*/
private Element getNodeByPath(Element root, String path)
{
String[] elements = path.split("/");
Element parentNode = root;
elementCycle:
for (String element : elements)
{
NodeList list = parentNode.getElementsByTagName("node");
int len = list.getLength();
for(int i = 0; i < len; i++)
{
Element node = (Element) list.item(i);
if (element.equals(node.getAttribute("name")))
{
parentNode = node;
continue elementCycle;
}
}
return null;
}
return parentNode;
}
/**
* Get the string values which are stored under the given node.
*
* @param root
* The root node of the source XML.
* @param path
* Path to the required node (names of the nodes starting from the
* root node (exclusive), separated by "/", e.g.
* "parameters/names").
*
* @return See above.
*/
private String[] getNodeStrings(Element root, String path)
{
Element parentNode = getNodeByPath(root, path);
if (parentNode == null)
throw new IllegalArgumentException("Cannot find node " + path + "!");
NodeList list = parentNode.getElementsByTagName("node-attribute");
int len = list.getLength();
String[] res = new String[len];
for(int i = 0; i < len; i++)
{
Element node = (Element) list.item(i);
res[i] = node.getAttribute("value");
}
return res;
}
/**
* Get the byte array which is stored under the given node.
*
* @param root
* The root node of the source XML.
* @param path
* Path to the required node (names of the nodes starting from the
* root node (exclusive), separated by "/", e.g.
* "parameters/data").
*
* @return See above.
*/
private byte[] getNodeByteArray(Element root, String path)
{
Element node = getNodeByPath(root, path);
if (node == null)
throw new IllegalArgumentException("Cannot find node " + path + "!");
node = (Element)node.getElementsByTagName("node-attribute").item(0);
String data = node.getAttribute("value");
return Base64.base64ToByteArray(data);
}
/**
* Return the element of the given multidimensional array which position
* position is specified by the given indexes.
*
* @param array
* Array from which the required element will be retrieved.
* @param indexes
* Array of indexes which specify the position of the required
* element in the array.
*
* @return See above.
*/
private Object getElement(Object array, int[] indexes)
{
Object[] arr = (Object[]) array;
int maxIndex = indexes.length - 1;
for (int i = 0; i < maxIndex; i++)
{
arr = (Object[]) arr[indexes[i]];
}
return arr[indexes[maxIndex]];
}
/**
* Get the OutputOperationType enumeration value for the given operation
* type and number of bytes written by this operation. See
* {@link OutputOperationType}
*
* @param put
* <code>true</code> if a PUT operation was performed,
* <code>false</code> if a DISPLAY operation was performed
*
* @param bytesWritten
* Number of bytes that has been written by this operation.
*
* @return See above.
*/
private OutputOperationType getOutputDataEnumByNumber(boolean put,
int bytesWritten)
{
if (put)
{
if (bytesWritten < 15)
return OutputOperationType.PUT_10;
else if (bytesWritten < 50)
return OutputOperationType.PUT_20;
else if (bytesWritten < 65)
return OutputOperationType.PUT_60;
else if (bytesWritten < 85)
return OutputOperationType.PUT_80;
else if (bytesWritten < 95)
return OutputOperationType.PUT_90;
else if (bytesWritten < 150)
return OutputOperationType.PUT_100;
else
return OutputOperationType.PUT_200;
}
else
{
if (bytesWritten < 15)
return OutputOperationType.DISPLAY_10;
else if (bytesWritten < 25)
return OutputOperationType.DISPLAY_20;
else if (bytesWritten < 35)
return OutputOperationType.DISPLAY_30;
else if (bytesWritten < 45)
return OutputOperationType.DISPLAY_40;
else if (bytesWritten < 200)
return OutputOperationType.DISPLAY_80;
else if (bytesWritten < 425)
return OutputOperationType.DISPLAY_250;
else
return OutputOperationType.DISPLAY_450;
}
}
/**
* Stores information about the current state of the output stream and acts
* as a helper for determining whether we have exceeded the buffer size.
*/
private class StreamInfo
{
/** The stream to which this information relates. */
private ProcessStream stream;
/**
* The set blocking operations that has been called while the stream
* was opened.
*/
private Set<BlockingOperation> blockingOperations = null;
/**
* Buffer sizes (which correspond the current stream state) for a flush
* action for different output operation types.
*/
private Map<OutputOperationType, Integer> flushLimits = null;
/**
* Buffer sizes (which correspond the current stream state) for a close
* action for different output operation types.
*/
private Map<OutputOperationType, Integer> closeLimits = null;
/**
* Maximum buffer size among the values contained into
* <code>flushLimits</code> map.
*/
private int maxFlushLimit;
/**
* Maximum buffer size among the values contained into
* <code>closeLimits</code> map.
*/
private int maxCloseLimit;
/**
* The list of output operations that has been performed using the
* corresponding stream.
*/
private List<OutputOperation> outputOperations = null;
/**
* Indicates whether the contained information should be refreshed
* because of changes into the stream state.
*/
private boolean pendingRefresh = true;
/**
* Cached sum of pseudo-bytes that have been written. See
* {@link #calculatePseudoBytesWritten(java.util.Map, int)}
*/
private int tempPseudoBytes = 0;
/**
* Maximum element (inclusive index) of <code>outputOperations</code>
* array that has already been added to <code>tempPseudoBytes</code>.
*/
private int maxCalcutatedIndex = -1;
/**
* Default c'tor.
*
* @param stream
* The stream to which this information relates.
*/
public StreamInfo(ProcessStream stream)
{
this.stream = stream;
}
/**
* Notify that a blocking operation has been performed while this stream
* was open.
*
* @param blockingOperation
* Blocking operation that was performed.
*/
public void notifyBlockingOperation(BlockingOperation blockingOperation)
{
if (blockingOperations == null)
blockingOperations = new HashSet<BlockingOperation>();
if (blockingOperations.add(blockingOperation))
{
pendingRefresh = true;
}
}
/**
* Notify that an output operation has been performed using this stream.
*
* @param type
* Type of the performed output operation.
* @param bytes
* Number of bytes that has been written by the output operation.
*/
public void notifyOutputOperation(OutputOperationType type, int bytes)
{
if (outputOperations == null)
outputOperations = new ArrayList<OutputOperation>();
outputOperations.add(new OutputOperation(type, bytes));
}
/**
* Get the set of blocking operation that has been performed while the
* stream was open.
*
* @return See above.
*/
public Set<BlockingOperation> getBlockingOperations()
{
return blockingOperations;
}
/**
* Determine whether the stream has exceeded the buffer size and the
* STOP condition should be raised.
*
* @param close
* <code>true</code> if we are performing a close operation,
* <code>false</code> if we are performing a flush operation.
* @return See above.
*/
public boolean shouldRaiseStop(boolean close)
{
if (pendingRefresh)
refreshInformation();
int limit = close ? maxCloseLimit : maxFlushLimit;
int bytes = calculatePseudoBytesWritten(close ? closeLimits :
flushLimits,
limit);
return bytes > limit;
}
/**
* Calculates the number of pseudo-bytes written through this stream.
* <p>
* Writing different number of bytes in a single operation may lead to
* different buffer sizes. E.g. if we are using only PUT "xxxx"
* operations we can output 40 bytes in this way, but if we are using
* only PUT "xxxxxxxxxx" operations we can output only 20 bytes.
* <p>
* Consider we have the following sequence of operations:<br>
* <code>PUT "xxxx"<br>
* PUT "xxxx"<br>
* PUT "xxxx"<br>
* PUT "xxxx"<br>
* PUT "xxxx"<br>
* PUT "xxxxxxxxxx"<br></code>
* We have 5 * 4 + 1 * 10 = 30 "real" bytes written.<br>
* But we have 5 * 4 * (40 / 40) + 1 * 10 * (40 / 20) = 40 pseudo-bytes
* written which equals the buffer size limit for this case.
*
* @param operationsMap
* Map which contains current buffer sizes for different output
* operation types.
* @param maxValue
* The maximum buffer size among the values contained in the
* <code>operationsMap</code>
*
* @return See above.
*/
private int calculatePseudoBytesWritten(
Map<OutputOperationType, Integer> operationsMap,
int maxValue)
{
int res = tempPseudoBytes;
if (outputOperations != null)
{
int len = outputOperations.size();
for (int i = maxCalcutatedIndex + 1 ; i < len; i++)
{
OutputOperation operation = outputOperations.get(i);
int divisor = operationsMap.get(operation.outputOperationType);
if (divisor == 0)
divisor = 1;
res += operation.bytesWritten * maxValue / divisor;
}
}
tempPseudoBytes = res;
return res;
}
/**
* Refresh the contained information according to the current stream
* state.
*/
private void refreshInformation()
{
if (flushLimits == null)
flushLimits = new HashMap<OutputOperationType, Integer>();
if (closeLimits == null)
closeLimits = new HashMap<OutputOperationType, Integer>();
int maxFlush = -1;
int maxClose = -1;
for (OutputOperationType output : OutputOperationType.values())
{
int val = getBufferSize(false, this, output);
flushLimits.put(output, val);
if (val > maxFlush)
maxFlush = val;
val = getBufferSize(true, this, output);
closeLimits.put(output, val);
if (val > maxClose)
maxClose = val;
}
maxFlushLimit = maxFlush;
maxCloseLimit = maxClose;
tempPseudoBytes = 0;
maxCalcutatedIndex = -1;
}
/**
* Container which describes a PUT/DISPLAY operation.
*/
private class OutputOperation
{
/** Type of the output operation. */
public OutputOperationType outputOperationType;
/** Number of bytes that has been written by this operation. */
public int bytesWritten;
/**
* Default c'tor.
*
* @param outputOperationType
* Type of the output operation.
* @param bytesWritten
* Number of bytes that has been written by this operation.
*/
public OutputOperation(OutputOperationType outputOperationType,
int bytesWritten)
{
this.outputOperationType = outputOperationType;
this.bytesWritten = bytesWritten;
}
}
}
}