/**
* This file is protected by Copyright.
* Please refer to the COPYRIGHT file distributed with this source distribution.
*
* This file is part of REDHAWK IDE.
*
* All rights reserved. This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html.
*
*/
package nxm.redhawk.prim;
import gov.redhawk.bulkio.util.BulkIOType;
import gov.redhawk.bulkio.util.BulkIOUtilActivator;
import gov.redhawk.sca.util.Debug;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import nxm.redhawk.lib.RedhawkOptActivator;
import nxm.redhawk.prim.data.BulkIOReceiver;
import nxm.sys.inc.Commandable;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.FileName;
import nxm.sys.lib.Midas;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Time;
import org.eclipse.core.runtime.CoreException;
import BULKIO.PrecisionUTCTime;
import BULKIO.StreamSRI;
/**
* This class connects to the specified CORBA host and receives data and writes
* it to a file. Generics are used to receive all types of data(Scalar/Complex,
* Char/Byte(octet),Short/Integer/Long/Float/Double).
* @since 10.2
*/
public class corbareceiver2 extends CorbaPrimitive implements IMidasDataWriter { //SUPPRESS CHECKSTYLE ClassName
/** name of FILE= argument. */
public static final String A_FILE = "FILE";
/** name of IOR= argument. */
public static final String A_IOR = "IOR";
/** Name of IDL= argument. */
public static final String A_IDL = "IDL";
/** Name of STREAMID= argument. */
public static final String A_STREAM_ID = "STREAMID";
/**
* Name of switch to set to false to NOT block pushPacket/write(..) when pipe doesn't have enough room,
* which will cause that pushPacket data to get drop
* @deprecated since 11.0, use {@link #SW_BLOCKING_OPTION} instead.
*/
@Deprecated
public static final String SW_BLOCKING = "/BLOCKING";
/**
* Name of switch to set the blocking option (Blocking, NonBlocking (drop data), FromSRI (use setting from StreamSRI.blocking)
* on what to do in pushPacket/write(..) when pipe doesn't have enough room.
* @since 11.0
*/
public static final String SW_BLOCKING_OPTION = "/BLOCKINGOPTION";
/** Name of switch to grab number of seconds to wait for SRI during open(). */
public static final String SW_WAIT = "/WAIT";
/** Name of switch to treat dataOctet as 8-bit unsigned integer (this will upcast format type to 16-bit signed integer to hold value). */
public static final String SW_TREAT_OCTET_AS_UNSIGNED = "/UNSIGNEDOCTET";
/** Name of switch to specify desired connection ID. */
public static final String SW_CONNECTION_ID = "/CONNECTIONID";
/**
* Name of switch to enable/disable increasing/growing output file's pipe size when
* incoming data packet size is larger than it.
*/
public static final String SW_GROW_PIPE = "/GROWPIPE";
/** Name of switch to set pipe size multiplier based on incoming data packet size (when larger than current pipe size). */
public static final String SW_PS_MULTIPLIER = "/PSMULT";
/** Name of switch to set override sample rate (use 0.0 for auto based on StreamSRI). */
public static final String SW_SAMPLE_RATE = "/SAMPLERATE";
private static final Debug TRACE_LOGGER = new Debug(RedhawkOptActivator.ID, corbareceiver2.class.getSimpleName());
/** sleep interval (milliseconds) for {@link #SW_WAIT}. */
private static final int SLEEP_INTERVAL = 100;
/** max seconds allowed since UTC (J1950) */
private static final double MAX_UTC_WSEC = Time.MAX_INPUT_WSEC + Time.J1950TOJ1970;
private static final double MIN_UTC_WSEC = Time.J1950TOJ1970;
/** blocking option for what to do when pipe is full */
static enum BlockingOption {
NONBLOCKING, FALSE, // <-- drop data
BLOCKING, TRUE,
FROMSRI // <-- use setting from current StreamSRI.blocking
};
/** the output file to write to */
private volatile DataFile outputFile = null;
private FileName fileName;
private String idl;
/** The configured SRI */
private volatile StreamSRI currentSri;
private BulkIOReceiver receiver;
/** the streamID to only receive data from (null for all streams). */
private String streamId;
/** blocking option when output pipe is full. */
private BlockingOption blockingOption;
/** override SRI's sample rate (1/xdelta for type 1000, 1/ydelta for type 2000). zero for none. */
private int sampleRate = 0;
private boolean connected;
private String portIor;
private BulkIOType bulkioType;
/** custom Port connection ID to use (when not null). */
private String connectionId;
/** allow increasing the pipe size when pushPacket data is larger than it. */
private boolean canGrowPipe;
/** multiplier of data packet size for setting pipe size (on {@link #outputFile} if it is less than data size. */
private int pipeSizeMultiplier;
private int newPipeSize;
public static String decodeIDL(final String idl) {
try {
return URLDecoder.decode(idl, "UTF-8");
} catch (final UnsupportedEncodingException e) {
return idl;
}
}
@Override
public synchronized int open() {
// MA is the argument list
this.fileName = MA.getFileName(corbareceiver2.A_FILE);
this.portIor = MA.getCS(corbareceiver2.A_IOR);
final String encoded_idl = MA.getCS(corbareceiver2.A_IDL);
this.idl = corbareceiver2.decodeIDL(encoded_idl);
this.streamId = MA.getCS(corbareceiver2.A_STREAM_ID, null);
BlockingOption defBlockingOption = BlockingOption.FROMSRI;
if (MA.isPresent(SW_BLOCKING)) { // backwards compatible mode
if (MA.getState(SW_BLOCKING, false)) {
defBlockingOption = BlockingOption.BLOCKING;
} else {
defBlockingOption = BlockingOption.NONBLOCKING;
}
}
this.blockingOption = MA.getSelection(corbareceiver2.SW_BLOCKING_OPTION, defBlockingOption);
boolean unsignedOctet = MA.getState(corbareceiver2.SW_TREAT_OCTET_AS_UNSIGNED);
this.connectionId = MA.getCS(corbareceiver2.SW_CONNECTION_ID, null);
this.canGrowPipe = MA.getState(corbareceiver2.SW_GROW_PIPE, true);
this.sampleRate = MA.getL(corbareceiver2.SW_SAMPLE_RATE, sampleRate);
setPipeSizeMultiplier(MA.getL(corbareceiver2.SW_PS_MULTIPLIER, 4));
BulkIOType newType = BulkIOType.getType(this.idl);
this.bulkioType = newType;
if (this.receiver == null) {
this.receiver = new BulkIOReceiver(this, newType, unsignedOctet, this.streamId);
}
// Check if we were able to connect to the Port
if (!initConnections()) {
return Commandable.ABORT; // since we are unable to connect to the Port
}
final double maxWaitForSRI = MA.getD(corbareceiver2.SW_WAIT, 1000); // in milliseconds
int numLoops = (int) (maxWaitForSRI / corbareceiver2.SLEEP_INTERVAL); // 0 or positive values will effect timeout
if (maxWaitForSRI < 0) {
numLoops = Integer.MAX_VALUE; // effectively infinity
}
while (this.currentSri == null && (numLoops-- > 0)) {
try {
wait(corbareceiver2.SLEEP_INTERVAL);
} catch (InterruptedException e) {
break;
}
}
this.outputFile = createOutputFile(this.currentSri, this.receiver);
this.outputFile.open();
corbareceiver2.TRACE_LOGGER.exitingMethod();
return Commandable.NORMAL;
}
@Override
public int process() {
if (newPipeSize > 0) {
setPipeSize(newPipeSize);
newPipeSize = 0;
}
return Commandable.NOOP;
}
@Override
public synchronized int close() {
corbareceiver2.TRACE_LOGGER.enteringMethod();
DataFile localOutputFile = this.outputFile;
this.outputFile = null;
if (localOutputFile != null) {
localOutputFile.close();
}
if (this.state != Commandable.RESTART) {
//Avoid hanging the UI if the CORBA call to the component fails to return
super.close();
this.currentSri = null;
}
return Commandable.NORMAL;
}
/**
* @return false if connection has not been or failed to initialize
*/
private boolean initConnections() {
if (!connected) {
try {
String ior = this.portIor;
BulkIOType type = this.bulkioType;
BulkIOReceiver receiver2 = this.receiver;
String desiredConnID = this.connectionId;
if (ior != null && !ior.trim().isEmpty() && type != null && receiver2 != null) {
BulkIOUtilActivator.getBulkIOPortConnectionManager().connect(ior, type, receiver2, desiredConnID);
} else {
return false;
}
connected = true;
} catch (CoreException e) {
throw new MidasException("Failed to connect port", e);
}
}
return connected;
}
@Override
protected void shutdown() {
final String ior = portIor;
final BulkIOType type = bulkioType;
final BulkIOReceiver receiver2 = receiver;
if (ior != null && !ior.trim().isEmpty() && type != null && receiver2 != null) {
BulkIOUtilActivator.getBulkIOPortConnectionManager().disconnect(ior, type, receiver2, this.connectionId);
}
this.connectionId = null;
this.receiver = null;
this.connected = false;
this.portIor = null;
this.bulkioType = null;
}
/**
* This will determine if we should continue processing the packet. To
* process, the endOfStream must be false and we must have previously
* received SRI.
*
* @param endOfStream true if the received stream has closed
* @return true if we should process the data
*/
private final boolean shouldProcessPacket(final boolean endOfStream, final byte type) {
if (this.state != Commandable.PROCESS || isStateChanged() || this.currentSri == null || this.outputFile == null) {
return false;
}
return true;
}
private DataFile createDefaultOutputFile() {
final String format = "S" + receiver.getMidasType();
DataFile newOutputFile = new DataFile(this, fileName, "1000", format, BaseFile.OUTPUT);
return newOutputFile;
}
private DataFile createOutputFile(final StreamSRI sri, final BulkIOReceiver receiver) {
if (sri == null) {
return createDefaultOutputFile();
}
boolean noSubSizeFromSRI = (sri.subsize <= 0); // true for 1000 stream, false for 2000 stream
final String fileType = (noSubSizeFromSRI) ? "1000" : "2000"; // SUPPRESS CHECKSTYLE AvoidInline
final String format = ((sri.mode == 0) ? "S" : "C") + receiver.getMidasType(); // SUPPRESS CHECKSTYLE AvoidInline
final DataFile newOutputFile = new DataFile(MA.cmd, fileName, fileType, format, BaseFile.OUTPUT);
final double sampleDelta = 1.0 / corbareceiver2.getSampleRateFor(this.sampleRate, sri); // zero will be ignored (not used)
double xdelta;
final boolean overrideSampleDelta = (this.sampleRate > 0);
if (overrideSampleDelta && noSubSizeFromSRI) {
xdelta = sampleDelta;
} else if (sri.xdelta > 0 && sri.xdelta <= Double.MAX_VALUE) { // ignore <= 0, Infinity and NaN
xdelta = sri.xdelta;
} else {
xdelta = 1.0;
}
newOutputFile.setXStart(sri.xstart);
newOutputFile.setXDelta(xdelta);
newOutputFile.setXUnits(sri.xunits);
if (!noSubSizeFromSRI) {
newOutputFile.setFrameSize(sri.subsize);
}
double ydelta;
if (overrideSampleDelta && !noSubSizeFromSRI) {
ydelta = sampleDelta;
} else if (sri.ydelta > 0 && sri.ydelta <= Double.MAX_VALUE) { // ignore <= 0, Infinity and NaN
ydelta = sri.ydelta;
} else {
ydelta = 1.0;
}
newOutputFile.setYStart(sri.ystart);
newOutputFile.setYDelta(ydelta);
newOutputFile.setYUnits(sri.yunits);
return newOutputFile;
}
private static boolean isRestartNeed4SriChange(final StreamSRI sri, final StreamSRI lastSRI) {
boolean shouldRestart = false;
if (lastSRI != null) {
shouldRestart |= (lastSRI.mode != sri.mode);
shouldRestart |= (lastSRI.subsize != sri.subsize);
shouldRestart |= (lastSRI.xdelta != sri.xdelta);
shouldRestart |= (lastSRI.xstart != sri.xstart);
shouldRestart |= (lastSRI.xunits != sri.xunits);
shouldRestart |= (lastSRI.ydelta != sri.ydelta);
shouldRestart |= (lastSRI.ystart != sri.ystart);
shouldRestart |= (lastSRI.yunits != sri.yunits);
// blocking, hversion, keywords, and streamID changes do not require a restart
} else {
shouldRestart = true;
}
return shouldRestart;
}
/**
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public synchronized void setStreamSri(String streamID, StreamSRI oldSri, StreamSRI newSri) {
corbareceiver2.TRACE_LOGGER.message("{0}: setStreamSri to {1} id={2} blocking={3}", getID(), newSri, newSri.streamID, newSri.blocking);
if (this.streamId == null || this.streamId.equals(streamID)) {
this.currentSri = newSri;
sendMessage("STREAMSRI", 1, this.currentSri);
if (state == Commandable.PROCESS) {
if (corbareceiver2.isRestartNeed4SriChange(newSri, oldSri)) {
doRestart();
}
} else if (state == Commandable.OPEN) {
notifyAll();
}
}
}
private synchronized void doRestart() {
corbareceiver2.TRACE_LOGGER.message("{0}: scheduling restart... {1}", getID(), this.outputFile);
setState(Commandable.RESTART);
}
/**
* This plots an arbitrary typed array of data. Type checking is performed
* by System.arraycopy(). The type of 'list' must match 'type'.
*
* @param dataArray the array of data to plot
* @param size the length of the data in the array
* @param type the NeXtMidas type of the data to plot (eg. Data.FLOAT)
*/
@Override
public void write(final Object dataArray, final int size, final byte type, final boolean endOfStream, PrecisionUTCTime time, final String streamId) {
if (!shouldProcessPacket(endOfStream, type)) {
return;
}
final DataFile localOutputFile = this.outputFile;
if (localOutputFile == null) {
return;
}
final int bufferSize = Data.getBPS(type) * size; // size of data to write in bytes
if (this.canGrowPipe && localOutputFile.getPipeSize() < bufferSize) { // increase pipe size if allowed (ticket #1554)
if (corbareceiver2.TRACE_LOGGER.enabled) {
corbareceiver2.TRACE_LOGGER.message("{0}: scheduling pipe size increase from {1} to ({2} x {3}) bytes for {4}",
getID(), localOutputFile.getPipeSize(), this.pipeSizeMultiplier, bufferSize, this.outputFile);
}
this.newPipeSize = bufferSize * this.pipeSizeMultiplier; // this change will be picked up in the process() method
}
if (!isBlocking()) { // === non-blocking option enabled (drop data if pipe is full) ===
if (M.pipeMode == Midas.PPAUSE) { // 1. pipe is PAUSED
corbareceiver2.TRACE_LOGGER.message("Dropping packet b/c pipe is PAUSED");
return; // 1b. drop packet, as write would block
}
if (!this.canGrowPipe && localOutputFile.getPipeSize() < bufferSize) {
// PASS - let packet through even though this might block, otherwise no data will ever be written
} else if (localOutputFile.getResource().avail() < bufferSize) { // 2. avail buffer is less than data/packet size
// Time.sleep(0.01); // <-- provide slight back-pressure so we don't spin CPU for Component that does NOT throttle data
corbareceiver2.TRACE_LOGGER.message("Dropping packet b/c not enough avail buffer without blocking write");
return; // 2b. drop packet since write would block
}
}
if (time != null && time.twsec <= MAX_UTC_WSEC && time.twsec >= MIN_UTC_WSEC && time.tfsec <= MAX_UTC_WSEC && time.tfsec >= MIN_UTC_WSEC) {
Time midasTime = new Time(time.twsec + Time.J1970TOJ1950, time.tfsec);
localOutputFile.setTimeAt(midasTime);
}
byte[] byteBuffer = new byte[bufferSize];
Convert.ja2bb(dataArray, 0, type, byteBuffer, 0, localOutputFile.dataType, size);
localOutputFile.write(byteBuffer, 0, byteBuffer.length);
return;
}
/**
* @return the current custom sample rate to override in SRI, zero for no override.
*/
public int getSampleRate() {
return sampleRate;
}
private static int getSampleRateFor(int sr, StreamSRI sri) {
if (sr > 0) {
return sr;
}
final int sampleRateFromSRI;
if (sri.subsize > 1) { // type 2000 stream
if (sri.ydelta > 0 && sri.ydelta <= Double.MAX_VALUE) { // ignore <= 0, Infinity and NaN
sampleRateFromSRI = (int) Math.rint(1 / sri.ydelta);
} else {
sampleRateFromSRI = 1; // invalid value from SRI, fallback to 1
}
} else { // type 1000 stream
if (sri.xdelta > 0 && sri.xdelta <= Double.MAX_VALUE) { // ignore <= 0, Infinity and NaN
sampleRateFromSRI = (int) Math.rint(1 / sri.xdelta);
} else {
sampleRateFromSRI = 1; // invalid value from SRI, fallback to 1
}
}
return sampleRateFromSRI;
}
/**
* @param newSRate the custom sample rate to override in SRI (zero for none);
* (1/xdelta for 1000 stream; 1/ydelta for 2000 stream).
*/
public void setSampleRate(int newSRate) {
if (newSRate <= 0) {
newSRate = 0;
}
int oldValue = corbareceiver2.getSampleRateFor(this.sampleRate, currentSri);
if (this.sampleRate != newSRate) {
this.sampleRate = newSRate;
int newValue = corbareceiver2.getSampleRateFor(this.sampleRate, currentSri);
if (oldValue != newValue) {
doRestart(); // restart since specified sample rate is different than in SRI
}
}
}
/**
* @return the blocking option for when output pipe is full
*/
public boolean isBlocking() {
switch (this.blockingOption) {
case FROMSRI:
StreamSRI sri = this.currentSri;
boolean retval = (sri != null) ? sri.blocking : false;
return retval;
case TRUE: /** fallthrough, same as Blocking */
case BLOCKING:
return true;
default: /** fallthrough, same as NonBlocking */
case FALSE: /** fallthrough, same as NonBlocking */
case NONBLOCKING:
return false;
}
}
public void setBlocking(boolean block) {
if (block) {
this.blockingOption = BlockingOption.BLOCKING;
} else {
this.blockingOption = BlockingOption.NONBLOCKING;
}
}
void setBlocking(BlockingOption blockingOption) {
this.blockingOption = blockingOption;
}
/** @since 11.0 */
public void setBlockingOption(String blockingOptionStrVal) {
this.blockingOption = BlockingOption.valueOf(blockingOptionStrVal);
}
/** @since 11.0 */
public BlockingOption getBlockingOption() {
return this.blockingOption;
}
/**
* @since 10.1
*/
public boolean isCanGrowPipe() {
return canGrowPipe;
}
/**
* @since 10.1
*/
public void setCanGrowPipe(boolean newValue) {
this.canGrowPipe = newValue;
MA.put(corbareceiver2.SW_GROW_PIPE, "" + newValue);
}
public int getPipeSizeMultiplier() {
return pipeSizeMultiplier;
}
/**
* @param newValue new pipe size multiplier (MUST be >= 1)
*/
public void setPipeSizeMultiplier(int newValue) {
if (newValue <= 0) {
throw new IllegalArgumentException("data size to pipe size multiplier must be greater than 0");
}
this.pipeSizeMultiplier = newValue;
MA.put(corbareceiver2.SW_PS_MULTIPLIER, "" + newValue);
}
public int getPipeSize() {
int retval = 0;
DataFile df = this.outputFile;
if (df != null) {
retval = df.getPipeSize();
}
return retval;
}
/**
* Change output file's pipe size (immediately)
* @param newValue new pipe size for output data file/pipe (in bytes)
*/
public synchronized void setPipeSize(int newValue) {
if (newValue <= 0) {
throw new IllegalArgumentException("pipe size (bytes) must be greater than 0");
}
DataFile df = this.outputFile;
if (df != null && df.getPipeSize() != newValue) {
corbareceiver2.TRACE_LOGGER.message("{0}: changing pipe size from {1} to {2} bytes for {3}", getID(), df.getPipeSize(), newValue, df);
// boolean changePipeMode = M.pipeMode == Midas.PRUN;
// if (changePipeMode) {
// M.pipeMode = Midas.PPAUSE; // change pipe mode to PAUSE to prevent DataFile.setPipeSize(..) from sleeping 0.2 sec
// }
df.setPipeSize(newValue);
// if (changePipeMode) {
// M.pipeMode = Midas.PRUN; // restore pipe mode back to RUN
// }
}
}
public StreamSRI getCurrentSri() {
return currentSri;
}
}