/*******************************************************************************
* 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 java.io.InputStream;
import java.io.OutputStream;
import org.eclipse.ecf.ipc.IPCException;
import org.eclipse.ecf.ipc.Utils;
import org.eclipse.ecf.ipc.fifo.FIFOImpl.PipeDirection;
/**
* A one-way interprocess communications channel.
* <UL>
* <LI><A href="#note">Note</A></LI>
* <LI><A href="#quickstart">Quickstart</A></LI>
* <LI><A href="#description">Description</A></LI>
* </UL>
* <A name="note"><H2>NOTE</H2></A> Because of the way that Windows implements named
* pipes, one process must call the {@link #create()} method before the named pipe can be
* used. This is in addition to whether that process will be exclusively reading from or
* writing to the pipe.
* <P>
* On Linux, a named pipe (FIFO) will persist after the process that created it
* terminates, allowing subsequent clients to use it without taking on a client or role.
* </P>
* <P>
* Unfortunately, using this information will introduce a platform dependency in the
* system.
* </P>
* <A name="quickstart"><H2>Quickstart</H2></A> <H3>Creator/Writer Process</H3>
* <P>
* <CODE>
* <PRE>
* byte[] buffer;
* String name;
* // create an instance for buffer and fill it with data
* // set name to a valid file name
* ...
* NamedPipe pipe = new NamedPipe(name);
* pipe.create();
* pipe.openWriter();
* pipe.write(buffer);
* </PRE>
* </CODE>
* <P>
* <H3>Reader Process</H3>
* <P>
* <CODE>
* <PRE>
* byte[] buffer;
* String name;
* // create an instance for buffer
* // set name to the same name as the writer is using
* ...
* NamedPipe pipe = new NamedPipe(name);
* pipe.openReader();
* int count = pipe.read(buffer);
* // process the data received
* ...
* </PRE>
* </CODE>
* </P>
* <A name="description"><H2>Description</H2></A> <A href="#note">Please see the note on
* client/server roles before using this class</A>
* <P>
* This class provides a named pipe IPC primitive. These are sometimes referred to as
* FIFOs or message queues.
* </P>
* <P>
* In this context a named pipe provides a one-way, synchronous communications channel
* between two processes. Note that this class only allows one-way communications even if
* the underlying mechanism the operating system supports is two-way.
* </P>
* <P>
* The class uses file naming <A href="../package-summary.html#file_naming"> as explained
* in the package description.</A> In the case of this class, the file name will refer to
* an operating system file. On Linux, this will be the name of the FIFO that is a file in
* the file system that implements the pipe. On windows, this will be a file that contains
* the actual name of the pipe.
* </P>
* <P>
* Before using an instance of the class, the {@link #openReader()} or
* {@link #openWriter()} method should be used to establish the role it will be taking
* when communicating. Attempting to use an instance without properly initializing may
* result in unpredictable behavior.
* </P>
* <P>
* Once the role has been established the {@link #read(byte[])},
* {@link #read(byte[], int, int)}, {@link #write(byte[])} and
* {@link #write(byte[], int, int)} methods, as appropriate, can be used to send or
* receive information.
* </P>
* <A name="virtual_file"><H2>Virtual File Name</H3></A>
* <P>
* This class uses virtual and
* actual files to implement the underlying primitive. On platforms such as Linux, the
* virtual and actual file names are the same and both refer to a special file on the
* system that represents the communications channel.
* </P>
* <P>
* In the case of Windows, named pipes must use a special file name of the form: {@code
* \\.\pipe\<name>}. Such file names cannot be used on Linux.
* </P>
* <P>
* In order to provide a reasonably platform independent solution, the Windows
* implementation of named pipes uses two file names for the pipe: a virtual name name and
* an actual name.
* </P>
* <P>
* The virtual name is basically any valid file name on the system. The file identified in
* the virtual name corresponds to an actual file in the system, which contains the actual
* name of the named pipe.
* </P>
* <P>
* The actual name is then used to identify the named pipe to Windows.
* </P>
*
* @author Clark N. Hobbie
*/
public class FIFO
{
public enum BlockingMode
{
Blocking,
NonBlocking;
public static BlockingMode toValueIgnoreCase(String s)
{
return (BlockingMode) Utils.toValueIgnoreCase(values(), s);
}
}
public static final int DEFAULT_BUFFER_SIZE = 1024;
private BlockingMode myBlockingMode;
public BlockingMode getBlockingMode()
{
return myBlockingMode;
}
public void setBlockingMode(BlockingMode blockingMode)
{
myBlockingMode = blockingMode;
}
private PipeDirection myDirection;
private int myTimeoutMsec;
private FIFOImpl myImpl;
public int getTimeoutMsec()
{
return myTimeoutMsec;
}
public void setTimeoutMsec(int timeout)
{
myTimeoutMsec = timeout;
}
protected FIFOImpl getImpl()
{
return myImpl;
}
protected void setImpl(FIFOImpl impl)
{
myImpl = impl;
}
/**
* Create a named pipe with the specified virtual name.
*
* @param virtualName
* The file to be used for the named pipe. See the class description for
* details about <A href="#virtual_file"> how and why the virtual file name is
* used. </A>
*/
public FIFO(String virtualName)
{
initialize(virtualName, DEFAULT_BUFFER_SIZE, -1);
}
public FIFO(String virtualName, int timeoutMsec)
{
initialize(virtualName, DEFAULT_BUFFER_SIZE, timeoutMsec);
}
protected void initialize(String virtualName)
{
initialize(virtualName, DEFAULT_BUFFER_SIZE, -1);
}
private void initialize(String virtualName, int bufferSize, int timeoutMsec)
{
FIFOImpl impl = new FIFOImpl(virtualName);
setTimeoutMsec(timeoutMsec);
impl.setBufferSize(bufferSize);
setImpl(impl);
}
public void create() throws IPCException
{
FIFOResult result = getImpl().create();
processCreateResult(result);
}
private void processCreateResult(FIFOResult result) throws IPCException
{
if (FIFOResult.SUCCESS != result.resultCode)
{
String msg = null;
switch (result.resultCode)
{
case FIFOResult.ERROR_ACCESS_DENIED:
msg = "access to the FIFO was denied";
break;
case FIFOResult.ERROR_UNKNOWN:
default:
msg = "An unknown error was encountered while trying to create the named pipe";
break;
}
throw new IPCException(msg);
}
}
protected String readActualName(String virtualName) throws IPCException
{
try
{
String s = Utils.readFile(virtualName);
return s;
}
catch (IOException e)
{
String msg = "An error was encountered while trying to read the actual "
+ "name of a named pipe from its virtual name.";
throw new IPCException(msg, e);
}
}
/**
* Open the named pipe, taking on the role of reader or writer.
* <P>
* On Linux, users of named pipes need to either read or write to a named pipe, they
* should not do both. Therefore this method establishes how the client is going to
* use the pipe and then connects to it.
* </P>
*
* @param direction
* The role the client wants to take on: reader or writer.
* @throws IPCException
* If a problem is encountered while trying to connect to the pipe.
*/
public void open(PipeDirection direction) throws IPCException
{
FIFOResult result = getImpl().open(direction);
processOpenResult(result);
}
private void processOpenResult(FIFOResult result) throws IPCException
{
if (FIFOResult.SUCCESS != result.resultCode)
{
switch (result.resultCode)
{
case FIFOResult.ERROR_INVALID_HANDLE:
{
String msg = "Attempt to listen to an unconnected named pipe. "
+ "Error code = " + result.errorCode;
throw new IPCException(msg);
}
default:
{
String msg = "An unknown error was encountered while trying to listen "
+ "for a client. " + "Error code = " + result.errorCode;
throw new IPCException(msg);
}
}
}
}
/**
* Connect to the named pipe, taking on the role of writer.
* <P>
* This method is equivalent to calling {@link #open(PipeDirection)} with an argument
* of {@link PipeDirection#Writer}.
* </P>
*
* @throws IPCException
* If a problem is encountered while trying to connect to the pipe.
* @see #open(PipeDirection)
*/
public void openWriter() throws IPCException
{
setDirection(PipeDirection.Writer);
open(PipeDirection.Writer);
}
/**
* Connect to the named pipe, taking on the role of reader.
* <P>
* This method is equivalent to calling {@link #open(PipeDirection)} with an argument
* of {@link PipeDirection#Reader}.
* </P>
*
* @throws IPCException
* If a problem is encountered while trying to connect to the pipe.
* @see #open(PipeDirection)
*/
public void openReader() throws IPCException
{
setDirection(PipeDirection.Reader);
open(PipeDirection.Reader);
}
protected PipeDirection getDirection()
{
return myDirection;
}
protected void setDirection(PipeDirection direction)
{
myDirection = direction;
}
protected String getActualName()
{
return getImpl().getActualName();
}
/**
* Read from the named pipe, blocking until data becomes available.
* <P>
* This method is equivalent to calling {@linkplain #read(byte[], int, int)
* read(buffer, 0, buff.length)}. See that class for additional details.
* </P>
*
* @param buffer
* The buffer where the data read from the pipe should be placed.
* @return The number of bytes actually read.
* @throws IPCException
* If a problem is encountered while reading from the named pipe.
* @see #read(byte[], int, int)
*/
public int read(byte[] buffer) throws IPCException
{
return read(buffer, 0, buffer.length);
}
/**
* Read some data from the named pipe, blocking if the data is not available.
* <P>
* This method will block if data is not available when it is called. The method may
* read less than the specified amount of data.
* </P>
* <P>
* It is an error on some platforms to read from a pipe that has been opened for
* writing.
* </P>
*
* @param buffer
* The buffer where the data read should be placed.
* @param offset
* Where the data should start in the buffer.
* @param length
* The size of the buffer, in bytes.
* @return The number of bytes read.
* @throws IPCException
* If a problem is encountered while trying to read from the pipe.
*/
public int read(byte[] buffer, int offset, int length) throws IPCException
{
return getImpl().read(buffer, offset, length);
}
/**
* Write some data to the named pipe.
* <P>
* This method is equivalent to calling {@linkplain #write(byte[], int, int)
* write(buffer, 0, buffer.length)}. Please see that method for additional details.
* </P>
*
* @param buffer
* The buffer that contains the data.
* @return The number of bytes actually written.
* @throws IPCException
* If a problem is encountered while writing the data.
*/
public int write(byte[] buffer) throws IPCException
{
return write(buffer, 0, buffer.length);
}
/**
* Write some data to the named pipe.
* <P>
* This method writes data to the named pipe and will block if the pipe is currently
* full.
* </P>
* <P>
* It is an error to write to a named pipe that the client opened as a reader.
* </P>
* <P>
* The method may perform a partial write, in which case only some of the data in
* the buffer was actually written. In that case, the return value will be
* less than the length parameter.
* </P>
*
* @param buffer
* The buffer that contains the data to write.
* @param offset
* Where in the buffer that the data starts.
* @param length
* The number of bytes of data to write.
* @return The number of bytes of data that were actually written.
* @throws IPCException
* If a problem is encountered while trying to write the data.
*/
public int write(byte[] buffer, int offset, int length) throws IPCException
{
return getImpl().write(buffer, offset, length, getTimeoutMsec());
}
public InputStream getInputStream(int timeoutMsec) throws IOException
{
try
{
open(PipeDirection.Reader);
return new FIFOInputStream(this, timeoutMsec);
}
catch (IPCException e)
{
throw new IOException("Error opening pipe", e);
}
}
public InputStream getInputStream() throws IOException
{
return getInputStream(-1);
}
public OutputStream getOutputStream() throws IOException
{
try
{
open(PipeDirection.Writer);
return new FIFOOutputStream(this);
}
catch (IPCException e)
{
throw new IOException("Error opening pipe", e);
}
}
public String getVirtualName()
{
return getImpl().getVirtualName();
}
public native void selectForWriting(int timeoutMsec);
}