Whisper.java

import java.io.*;
import java.util.Random;
import java.text.NumberFormat;

/**
The game of "Whispers", passing a message from thread to thread 
with random changes at each stage.  An example of piped input/output 
streams communicating between threads.
*/
public class Whisper
{
    public static void main(String[] argv)
    {
        int nworkers = 8;
        int i;

        try {
            // Open input file.
            File infile = new File("pips.txt");
            FileInputStream instream = 
                new FileInputStream(infile);

            // Open output file.
            File outfile = new File("pips.out");
            FileOutputStream outstream = 
                new FileOutputStream(outfile);

            // Create the worker threads.
            Thread[] threads = new Thread[nworkers];
            InputStream in = null;
            OutputStream out = null;
            InputStream outin = null;
            for (i=0; i<nworkers; i++) {
                if (i == 0)
                    in = instream;
                else
                    in = outin;
                if (i == nworkers-1)
                    out = outstream;
                else {
                    out = new PipedOutputStream();
                    outin = new PipedInputStream(
                        (PipedOutputStream)out);
                    }
                threads[i] = new Mutator(i,0.05,3,in,out);
                }

            // Start all threads.
            for (i=nworkers-1; i>=0; i--)
                threads[i].start();

            // Monitor thread progress.
            NumberFormat nf = NumberFormat.getIntegerInstance();
            nf.setGroupingUsed(false); // No commas in format.
            while (true) {
                boolean any = false;
                for (i=0; i<nworkers; i++) {
                    if (threads[i].isAlive()) {
                        String ss = nf.format(
                            ((Mutator)threads[i]).charCount());
                        ss = "           " + ss + " ";
                        System.out.print(ss.substring(ss.length()-8));
                        any = true; // At least one thread still runs.
                        }
                    else
                        System.out.print("   done ");
                    }
                System.out.println();
                if (!any) // No more threads running.
                    break;
                Thread.sleep(500);
                }
            }
        catch(IOException e_io) {
            System.out.println("IO exception in main: " + e_io);
            }
        catch(InterruptedException e_int) {
            System.out.println("Interrupted!");
            }
        }
}


/**
An instance of this class copies its input to its output, mutating
each character with probability <code>mutationRate</code> and
pausing no more than <code>maxSleep</code> milliseconds between
characters.
*/
class Mutator extends Thread
{
    public Mutator(int id,double mutationRate,int maxSleep,
        InputStream in,OutputStream out)
    {
        this.id = id;
        this.mutationRate = mutationRate; // Mutation rate.
        this.maxSleep = maxSleep;
        this.in = in;
        this.out = out;
        this.rand = new Random();
        this.count = 0;
    }

    public void run()
    {
        try {
            while (true) {
                int c = in.read();
                if (c == -1)
                    break;
                out.write((int)mutate((char)c,mutationRate,rand));
                count++;
                Thread.sleep(rand.nextInt(maxSleep));
                }
            in.close();
            out.close();
            }
        catch(InterruptedException e_int) {
            System.out.println("Int(" + id + ": " + e_int);
            }
        catch(IOException e_io) {
            System.out.println("IO(" + id + "): " + e_io);
            }
    }

    /**
    How many characters has this thread processed.
    */
    synchronized public int charCount()
    {
        return count;
    }

    /**
    Mutate character '<code>c</code>' with probability 
    '<code>mrate</code>' using the supplied randome number generator.
    */
    private char mutate(char c,double mrate,Random rand)
    {
        String replacements =
            "abcdefghijklmnopqrstuvwxyz" +
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
            "0123456789";
        if (Character.isLetterOrDigit(c))
            if (rand.nextDouble() < mrate) {
                int n = rand.nextInt(replacements.length());
                c = replacements.charAt(n);
                }
        return c;
    }

    private int id = 0;
    private double mutationRate = 0.0;
    private int maxSleep = 0;
    private InputStream in;
    private OutputStream out;
    private Random rand;
    private int count;
}