/*******************************************************************************
* Copyright (c) 2009 Clark N. Hobbie
* 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
*
* Contributors:
* Clark N. Hobbie - initial API and implementation
*******************************************************************************/
package org.eclipse.ecf.ipc.fifo;
import java.io.IOException;
import org.eclipse.ecf.ipc.IPCException;
import org.eclipse.ecf.ipc.IPCPackage;
import org.eclipse.ecf.ipc.Utils;
/**
* An internal class that provides the underlying system calls needed to implement
* named pipes.
* <P>
* This class is implementation dependent and not intended for general use.
* </P>
*
* @author Clark N. Hobbie
*/
public class FIFOImpl
{
static {
IPCPackage.initializePackage();
}
public static final int DIRECTION_READER = 0;
public static final int DIRECTION_WRITER = 1;
public static final int BLOCKING_MODE_BLOCKING = 1;
public static final int BLOCKING_MODE_NON_BLOCKING = 2;
public static final int SELECT_READING = 1;
public static final int SELECT_WRITING = 2;
public static final int SELECT_BOTH = 3;
/**
* The role a client is taking (reader/writer) when using a named pipe. <H2>NOTE</H2>
* This class really belongs in {@link FIFO} and will probably be moved there in
* the near future.
*
* @author Clark N. Hobbie
*/
public enum PipeDirection
{
Reader(DIRECTION_READER), Writer(DIRECTION_WRITER);
public int jniValue;
private PipeDirection(int direction)
{
jniValue = direction;
}
}
public FIFOImpl(String virtualName)
{
initialize(virtualName);
}
protected void initialize(String virtualName)
{
setVirtualName(virtualName);
}
private native void createImpl(FIFOResult result);
public FIFOResult create() throws IPCException
{
setCreator(true);
processVirtualActualFile();
FIFOResult result = new FIFOResult();
createImpl(result);
if (result.resultCode == FIFOResult.SUCCESS)
{
setHandle(result.handle);
}
return result;
}
private void processVirtualActualFile() throws IPCException
{
String suffix = Long.toString(System.currentTimeMillis());
//
// if the platform DOES NOT have any particular requirements for the name
// of a pipe, then use the virtual name as the actual name.
//
String actual = toActualName(suffix);
if (null == actual)
{
actual = getVirtualName();
}
//
// otherwise, write the actual name into the virtual file.
//
else
{
try
{
actual = Utils.readOrCreate(getVirtualName(), actual);
}
catch (IOException e)
{
String msg =
"Error trying to read/write virtual file: " + getVirtualName();
throw new IPCException(msg,e);
}
}
setActualName(actual);
}
private native void writeImpl(FIFOResult result, byte[] buffer, int offset,
int length, int timeoutMsec);
/**
* Try to write a buffer of data within the specified time frame.
* <P>
* It is possible for a FIFO to have enough data written to it that it runs
* out of buffer space. When that happens the writer must wait until the
* reader reads enough of the data to free up some more buffer space.
* </P>
*
* <P>
* The timeout specifies how long the caller is willing to wait for buffer
* space to free up. A value of less than 0 means that the caller will wait
* "forever." A value of 0 means that the caller will not wait at all --- if
* it is not possible to write immediately, then throw a timeout.
* </P>
*
* <P>
* Any other value specifies the number of milliseconds that the caller will
* wait for the space to free up.
* </P>
*
* <P>
* Note that a wait will only occur if the FIFO is full --- in all other
* situations, the write will complete without delay.
* </P>
*
* @param buffer
* The buffer that contains the data to write.
* @param offset
* The offset within the buffer that the data starts.
* @param length
* The number of bytes to try and write.
* @param timeoutMsec
* See description. The number of milliseconds to wait for a
* write to become possible.
* @return The number of bytes actually written.
* @throws IPCException
* If the caller specified a wait time and this elapsed before
* anything could be written, then this exception is thrown.
*/
public int write(byte[] buffer, int offset, int length, int timeoutMsec) throws IPCException
{
if (getDirection() != PipeDirection.Writer)
{
throw new IPCException("Attempt to write to a read-only pipe.");
}
FIFOResult result = new FIFOResult();
writeImpl(result, buffer, offset, length, timeoutMsec);
if (result.resultCode != FIFOResult.SUCCESS)
{
String msg = "Error writing named pipe, error code = " + result.errorCode;
throw new IPCException(msg);
}
return result.byteCount;
}
private native void readImpl(FIFOResult result, byte[] buffer, int offset,
int length, int timeoutMsec);
public int read(byte[] buffer, int offset, int length) throws IPCException
{
return read(buffer, offset, length, -1);
}
public int read(byte[] buffer, int offset, int length, int timeoutMsec) throws IPCException
{
if (PipeDirection.Reader != getDirection())
{
throw new IPCException("Attempt to read a write-only pipe.");
}
FIFOResult result = new FIFOResult();
readImpl(result, buffer, offset, length, timeoutMsec);
if (result.resultCode == FIFOResult.ERROR_PIPE_CLOSED)
{
return -1;
}
if (result.resultCode != FIFOResult.SUCCESS)
{
String msg = "Error reading named pipe, code = " + result.errorCode;
throw new IPCException(msg);
}
return result.byteCount;
}
private native void openImpl(FIFOResult result, int direction);
public FIFOResult open(PipeDirection direction) throws IPCException
{
setDirection(direction);
if (null == getActualName())
{
processVirtualActualFile();
}
FIFOResult result = new FIFOResult();
openImpl(result, direction.jniValue);
setHandle(result.handle);
return result;
}
private String myActualName;
private boolean myCreator;
private PipeDirection myDirection;
private long myHandle;
private int myBufferSize;
private String myVirtualName;
public String getVirtualName()
{
return myVirtualName;
}
public void setVirtualName(String virtualName)
{
myVirtualName = virtualName;
}
protected int getBufferSize()
{
return myBufferSize;
}
protected void setBufferSize(int bufferSize)
{
myBufferSize = bufferSize;
}
protected String getActualName()
{
return myActualName;
}
protected PipeDirection getDirection()
{
return myDirection;
}
protected int getDirectionInt()
{
return myDirection.jniValue;
}
protected long getHandle()
{
return myHandle;
}
protected void setActualName(String actualName)
{
myActualName = actualName;
}
public void setCreator(boolean creator)
{
myCreator = creator;
}
protected void setDirection(PipeDirection direction)
{
myDirection = direction;
}
protected void setHandle(long handle)
{
myHandle = handle;
}
protected boolean isCreator()
{
return myCreator;
}
public native void createNonBlocking(FIFOResult result, String name);
public native void readNonBlocking(FIFOResult result, byte[] buffer, int start, int length, int timeoutMsec);
public native void writeNonBlocking(FIFOResult result, byte[] buffer, int start, int length, int timeoutMsec);
/**
* Return a name that the underlying OS can use for a named pipe, given a string
* that is more or less unique.
* <P>
* Currently, all platforms that support named pipes use a file name to identify
* the named pipe. This method does several things. First of all, it signals
* to ECF IPC whether or not the OS uses special names for pipes.
* </P>
* <P>
* Windows, for example, requires that all named pipes occur in the directory,
* "\\.\pipe" whereas linux has no such requirement.
* </P>
* <P>
* If the underlying OS has a special naming requirement, then it should return
* a name that it can use, given a suffix string.
* </P>
* <P>
* On windows, for example, if one were to supply "123456" as the suffix, the method
* should return "\\.\pipe\123456"
* </P>
* <P>
* If the underlying OS does not have a special naming requirement, then it should
* return null. Therefore on linux, this method should return null.
* </P>
*
* @param suffix A string that the actual name should be based on. See description for
* details.
* @return A suitable name if the platform requires that named pipes use a particular
* format, otherwise null.
*/
public static native String toActualName(String virtualName);
/**
* Connect to the underlying FIFO.
*/
public native void connectNonBlocking(FIFOResult result);
/**
* Determine if the FIFO is ready for reading, writing, or both.
* <P>
* This method is similar to the old Unix select system call, which would look
* for file handles read to read/write.
* </P>
* <P>
* The call takes the amount of time the caller wants to wait for I/O to
* be ready on the FIFO. A value of less than 0 means wait until ready.
* A value of 0 means poll the FIFO but do not wait. Any other value is
* the number of milliseconds to wait for the FIFO to be ready.
* </P>
* <P>
* If the client is the reader, then the method will wait until data is
* ready. If the client is a writer, it will wait for writing to become
* non-blocking.
* </P>
*
* @param timeouitMsec
*/
public native void select(FIFOResult result, int timeoutMsec);
}