/*******************************************************************************
* 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.sharedmemory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.Charset;
import org.eclipse.ecf.ipc.IPCException;
import org.eclipse.ecf.ipc.IPCException.Errors;
/**
* <P>
* A shared memory segment.
* </P>
* <H2>Description</H2>
* <P>
* This class wraps the {@link java.nio.MappedByteBuffer} class to make it a little easier
* to use.
* </P>
* <P>
* {@link #quickstart}
* </P>
* <P>
* Instances of this class must correspond to a file on the host system that is read/write
* to the running process. <A name="quickstart">
* <H2>Quickstart</H2> </A>
* <UL>
* <LI>Create or connect to an existing segment via {@link #SharedMemory(String, int)}.</LI>
* </LI>
* <LI>Read from the segment via {@link #read(byte[], int, int, int)}.</LI>
* <LI>Write to the segment via {@link #write(byte[], int, int, int)}.</LI>
* <LI>You do not need to explicitly close your connection.</LI>
* </UL>
*
* @see java.nio.MappedByteBuffer
* @author Clark N. Hobbie
*/
public class SharedMemory
{
protected File segmentFile;
protected MappedByteBuffer byteBuffer;
protected int size;
protected FileChannel channel;
private FileLock myLock;
private boolean myFileCreator;
public boolean isFileCreator()
{
return myFileCreator;
}
public void setFileCreator(boolean fileCreator)
{
myFileCreator = fileCreator;
}
public MappedByteBuffer getByteBuffer()
{
return byteBuffer;
}
public void setByteBuffer(MappedByteBuffer byteBuffer)
{
this.byteBuffer = byteBuffer;
}
public boolean isConnected()
{
return byteBuffer != null;
}
public File getSegmentFile()
{
return segmentFile;
}
public int getSize()
{
return size;
}
public SharedMemory()
{
}
public SharedMemory(File file, int size) throws IPCException
{
connect(file, size);
}
public SharedMemory(String fileName, int size) throws IPCException
{
connect(fileName, size);
}
/**
* Connect this segment to the underlying shared memory segment.
*
* @param name
* The name of the file for the segment.
* @param size
* The size of the segment.
*/
public void connect(File file, int size) throws IPCException
{
if (null != this.byteBuffer)
{
throw new IPCException(Errors.AlreadyConnected);
}
myFileCreator = false;
try
{
myFileCreator = file.createNewFile();
}
catch (IOException e)
{
throw new IPCException(Errors.ExceptionCreatingFile, e);
}
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "rw");
}
catch (FileNotFoundException e)
{
throw new IPCException(Errors.ErrorOpeningMappingFile, e);
}
channel = raf.getChannel();
try
{
this.byteBuffer = channel.map(MapMode.READ_WRITE, 0, size);
}
catch (IOException e)
{
throw new IPCException(Errors.ErrorCreatingMap, e);
}
this.segmentFile = file;
this.size = size;
}
public void connect(String fileName, int size) throws IPCException
{
File file = new File(fileName);
connect(file, size);
}
/**
* Write an array of bytes into a specified location in the segment, starting with a
* particular byte in the buffer and continuing for a specified number of bytes.
*
* @param buf
* The data to write.
* @param buffOffset
* The location, relative to the start of the buffer, where the data should be
* obtained.
* @param bufLength
* The number of bytes to write.
* @param segmentOffset
* The location in the buffer where the data should be written.
* @throws IPCException
* The method will throw this exception if the segment is not actually
* connected to the underlying shared memory block.
*/
public void write(byte[] buf, int buffOffset, int bufLength, int segmentOffset)
throws IPCException
{
try
{
if (null == this.byteBuffer)
{
throw new IPCException(Errors.NotConnected);
}
this.byteBuffer.position(segmentOffset);
this.byteBuffer.put(buf, buffOffset, bufLength);
}
catch (java.nio.BufferOverflowException e)
{
System.out.println("segment size = " + this.size + ", segmentOffset = "
+ segmentOffset + ", length = " + bufLength);
throw e;
}
}
public void write(byte value, int offset)
{
this.byteBuffer.position(offset);
this.byteBuffer.put(value);
}
public void write(byte[] buf) throws IPCException
{
write(buf, 0, buf.length, 0);
}
public void write(int value, int offset)
{
byte b = (byte) value;
write(b, offset);
}
public void write(byte[] buf, int segmentOffset) throws IPCException
{
write(buf, 0, buf.length, segmentOffset);
}
/**
* <P>
* Read bytes up to a specified number of bytes into the provided buffer.
* </P>
* <P>
* This method returns the number of bytes that were actually read. If fewer bytes are
* left in the segment than requested, then the method returns the number of bytes
* read.
* </P>
*
* @param buf
* The buffer where the bytes read should be stored.
* @param boffset
* The offset into buf where the bytes should be placed.
* @param length
* The number of bytes to try to read.
* @param soffset
* The offset within the segment where the read should start.
* @return The number of bytes actually read. See description for details.
* @throws IPCException
* This method throws this exception with the error set to
* {@link Errors#NotConnected} if the instance has not been connected
* to a segment yet.
*/
public int read(int soffset, byte[] buf, int boffset, int length) throws IPCException
{
if (null == byteBuffer)
{
throw new IPCException(Errors.NotConnected);
}
if (length > byteBuffer.remaining())
length = byteBuffer.remaining();
byteBuffer.position(soffset);
byteBuffer.get(buf, boffset, length);
return length;
}
/**
* <P>
* Read bytes from the segment into a buffer, storing them in a location offset from
* the start of the buffer.
* </P>
* <P>
* This method is equivalent to calling:
* </P>
* <CODE>
* <PRE>
* read(0, buf, bufferOffset, buf.length);
* </PRE>
* </CODE>
*
* @param buf
* The buffer where the bytes read should be placed.
* @param bufferOffset
* The location in the buffer where the bytes read should start.
* @return The number of bytes actually read.
* @throws IPCException
* {@link #read(int, byte[], int, int)}.
* @see #read(int, byte[], int, int)
*/
public int read(byte[] buf, int bufferOffset) throws IPCException
{
return read(0, buf, bufferOffset, buf.length);
}
/**
* Read bytes from the start of the segment.
* <P>
* This method is equivalent to calling
* </P>
* <CODE>
* <PRE>
* read(0, buf, 0, buf.length);
* </PRE>
* </CODE>
* <P>
* See that method for details.
* </P>
*
* @param buf
* The buffer where bytes read should be placed.
* @return The number of bytes read.
* @throws IPCException
* see {@link #read(int, byte[], int, int)}.
* @see {@link #read(int, byte[], int, int)}
*/
public int read(byte[] buf) throws IPCException
{
return read(0, buf, 0, buf.length);
}
/**
* Read in data from the segment, starting at a particular location in the segment.
* <P>
* This method is equivalent to calling
* </P>
* <CODE>
* <PRE>
* read(segmentOffset, buf, 0, buf.length);
* </PRE>
* </CODE>
* <P>
* See that method for details.
* </P>
*
* @param segmentOffset
* The byte index into the segment where reading should start.
* @param buf
* Where the bytes read should be placed.
* @return The number of bytes actually read.
* @throws IPCException
* see {@link #read(int, byte[], int, int)}
* @see #read(int, byte[], int, int)
*/
public int read(int segmentOffset, byte[] buf) throws IPCException
{
return read(segmentOffset, buf, 0, buf.length);
}
/**
* Return the byte at a particular location in the segment.
*
* @param offset
* The location of the byte in the segment.
* @return The value of the byte.
* @throws IndexOutOfBoundsException
* If offset is negative or larger than the size of the segment minus 1.
*/
public byte getByte(int offset) throws IPCException
{
return byteBuffer.get(offset);
}
/**
* Reserve the shared memory segment.
* <P>
* It is not clear from the JRE documentation whether or not locking the segment will
* stop modifications to the segment or not. {@linkplain FileChannel#lock() for
* details.}
*
* @throws IOException
*/
public void lock() throws IOException
{
myLock = channel.lock();
}
public void unlock() throws IOException
{
myLock.release();
}
/**
* Put some data at a location.
* <P>
* The entire contents of the buffer are written to a position in the segment defined
* by the offset parameter. If the amount of data exceeds the size of the segment, the
* method throws an exception.
* </P>
*
* @param offset
* The location in the segment, in bytes, where the data will be written.
* @param data
* The data to write. The contents of the array will be written.
* @throws IPCException
*/
public void put(int offset, byte[] data) throws IPCException
{
write(data, offset);
}
/**
* <P>
* Retrieve a line of text from the segment.
* </P>
* <P>
* This method goes to the specified location in the segment and tries to read in
* characters. It continues until it hits the end of the segment, or it encounters the
* end of the string. The end of a string is denoted by any of the following sequences
* of characters:
* </P>
* <UL>
* <LI>'\n'</LI>
* <LI>'\r'</LI>
* <LI>'\n\r'</LI>
* </UL>
* <P>
* The method uses the underlying character set as defined by
* {@link Charset#defaultCharset()}
* </P>
* <P>
* The supplied buffer is used to determine the maximum length of the string in that,
* if no line termination has been encountered before a number of bytes equal to the
* size of the buffer are read, then the characters accumulated so far are returned.
* </P>
* <P>
* Any line termination character(s) are not included in the string returned.
* </P>
*
* @param offset
* The location in the segment to start reading.
* @param buffer
* A buffer to contain the bytes from the shared memory segment.
* @return The resulting string.
* @throws IOException
* If the method tries to read past the end of the segment, either because it
* started outside the segment or because the string did not have any line
* termination.
* @throws IPCException
* This exception is thrown if the object is not currently connected to a
* shared memory segment.
*/
public String getLine(int offset, byte[] buffer) throws IOException, IPCException
{
StringBuffer sb = new StringBuffer();
//
// For the sake of efficiency, try to read a block of 256 bytes
//
int count = read(offset, buffer);
ByteBuffer bb = ByteBuffer.wrap(buffer, 0, count);
CharBuffer cb = Charset.defaultCharset().decode(bb);
int charCount = cb.length();
for (int index = 0; index < charCount; index++)
{
char c = cb.get();
if (c == '\n' || c == '\r')
break;
sb.append(c);
}
return sb.toString();
}
/**
* Return a string from the specified location in the segment; the string may only
* take the specified number of bytes.
* <P>
* This method is equivalent to calling
* </P>
* <CODE>
* <PRE>
* byte[] buf = new byte[maxBytes];
* getLine(offset, buf);
* </PRE>
* </CODE>
* <P>
* See that method for details.
* </P>
* <P>
* If the underlying string does not end after maxBytes, the method returns the
* substring that takes up that number of bytes.
* </P>
*
* @param offset
* The location within the segment where the string starts.
* @param maxBytes
* The maximum number of bytes for the string.
* @return The string found.
* @throws IOException
* See {@link #getLine(int, byte[])}.
* @throws IPCException
* See {@link #getLine(int, byte[])}.
* @see #getLine(int, byte[])
*/
public String getLine(int offset, int maxBytes) throws IOException, IPCException
{
byte[] buffer = new byte[maxBytes];
return getLine(offset, buffer);
}
public void putString(int offset, String s)
{
byte[] buff = s.getBytes();
byteBuffer.position(offset);
byteBuffer.put(buff);
}
public void putLine(int offset, String s)
{
s = s + '\n';
putString(offset, s);
}
}