A software architecture is a set of components connected in standardized ways. For example, you are probably familiar with the idea of a client-server architecture. In that type of architecture there are typically one server component and many client components. The clients are usually connected to the server and communicate in a request/response fashion. That is, the client requests something from the server and the server responds to the client, fulfilling the request.
Your task is to develop a generic software pipe and filter architecture in Java. A pipe and filter architecture is made up of components called filters, connected in a chain. That is, the output of one filter serves as the input to the next filter in the chain. A filter has exactly one input stream and one output stream. Filters may process/transform the data as it flows along the chain. The actual effect of passing data along the chain depends on which filters make up the chain and the order in which they are connected.
To work properly, applications that use this architecture must not connect the filters in cycles (i.e. the directed graph of filters with edges showing the connections from the output of each filter to the input of another filter does not have a cycle). Also, filters must not communicate with each other except through their input and output streams (that includes reading and writing files).
Consider the following example:
C1 - reads a stream of characters, interprets them as integers, computes the running sum of the last 3 integers, and outputs the averages as a stream of characters.
C2 - reads a stream of characters, interprets them as integers, computes the square of each integer, and outputs the squares as a stream of characters.
C3 - reads a stream of characters, interprets them as integers, deletes any value over 1,000,000 , and outputs the remaining integers as a stream of characters.
Connections: C1's input stream is a file and it's output stream is connected to the input stream of C2. C2's output stream is connected to the input stream of C3. The output stream of C3 is System.out.
Notice that since the filters all take character streams as input and have no dependencies on each other, they can be reordered to produce a different effect.
Example 2:
Connections: C2's input stream is a file and it's output stream is connected to the input stream of C1. C1's output stream is connected to the input stream of C3. The output stream of C3 is System.out.
Each filter should consist of three threads, two buffers, one input stream and one output stream. The read thread takes data from the filter's input stream and places it in the input buffer. The compute thread removes data from the input buffer, processes/transforms it, and writes its output to the output buffer. The write thread removes data from the output buffer and writes it to the output stream. Threads must exit properly, meaning that they should read from a buffer until no more data can appear in the buffer (see FilterBuffer.read()), notify a buffer when there will be no more writes into it (see FilterBuffer.close()), and exit when they can receive no more input data and have produced all their output data.
All threads must respect normal buffer semantics. That is, they must not write to a full buffer, read from an empty buffer, nor lose, corrupt or duplicate data items. You may assume that all data items in buffers are of type char, and the filter is responsible for conversion to other data types as specified.
Here are the interfaces for the Filter and FilterBuffer interfaces, with corresponding FilterImpl and FilterBufferImpl classes, that you must write:
public interface Filter {
void setConnection(java.io.Reader in, java.io.Writer out);
java.io.Reader getInputConnection();
java.io.Writer getOutputConnection();
FilterBuffer getInputBuffer();
FilterBuffer getOutputBuffer();
void startFilter(); // start computing
}
public class FilterImpl implements Filter {
FilterImpl(java.io.Reader in, java.io.Writer out, int maxReadBufSize, int maxWriteBufSize, Runnable compute);
}
public interface FilterBuffer {
char[] read (); // returns current contents of buffer and clears it. Returns a zero-length array if its internal buffer is empty and the FilterBuffer has been closed
void write(char[] str) throws bufTooSmallException; // write str to buffer if str.length is less than maxBufferSize, otherwise throws exception
int getMaxBufferSize();
void close(); // informs the FilterBuffer that no more data will be written to it
void flush(); // doesn't return until the buffer is empty
}
public class FilterBufferImpl implements FilterBuffer {
FilterBufferImpl(int maxSize);
}
You must also write filters for the following applications:
A
sample driver program that instantiates a tail, a replace
and a unique filter
and connects them properly is given in Text.java .
We strongly suggest using the logging client and server from Project 1 to keep track of all the interesting events for threads that will be created for the filter applications. We will be giving further instructions soon about which events must be logged in your code for submission (meaning inserting calls to myLoggingClient.writeRecord(), but the obvious ones are thread start and exit. We will also be giving you our (presumably working) version of Project 1 to use for testing.
Here are the additional instructions for logging events. You should create a myLoggingClient object in each filter object, connected to a myLoggingServer whose location is passed in on the command line as in project 1 (IP address and port specified with -D switches to the driver programs Erast.java and Text.java). For testing, you will also need to start a myLoggingServer on that machine, listening on that port.
Each filter that is created will start three threads (read, write, compute), and should log the thread start and exit events for each of those threads. Each thread should get a unique ID via a call to myCounter, and use that value as the ID string for the myLoggingClient.writeRecord() call, prepending the string "ThreadType thread " (i.e.., "ThreadType thread i", where ThreadType is one of Read, Write, or Compute, and i is the value returned by myCounter). The timestamp field should come from a call to myTimeStamper. Finally, the event string should be either "Thread started" or "Thread exited". You can use our logging server/client Java code for project 1, which is located here.