6.031
6.031 — Software Construction
Spring 2020

Code Examples for Reading 25

Download

You can also download a ZIP file containing this code.

Seems to be necessary to have a triple-backtick block at the start of this page,
otherwise all the pre/code tags below aren't syntax highlighted.
So create a hidden one.

Counter.java

package counter;

import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;

/** A mutable incrementing counter that calls its listeners each time it reaches a new number. */
public class Counter {

    /** A listener for this counter. */
    public interface NumberListener {
        /** Called when the counter increments.
          * @param number the new number */
        public void numberReached(BigInteger number); 
    }

    /**
     * Example client code, trying out a Counter with a couple listeners.
     * @param args unused
     */
    public static void main(String[] args) {
        Counter counter = new Counter();

        // listen for prime numbers
        counter.addNumberListener(number -> {
            if (number.isProbablePrime(100)) {
                System.out.println(number + " prime");
            }
        });

        // listen for just one number, then stop listening
        counter.addNumberListener(new NumberListener() {
            public void numberReached(BigInteger number) {
                System.out.println(number);
                counter.removeNumberListener(this);
            }
        });

        // crank the counter in a background thread
        new Thread(() -> {
            while (true) {
                counter.increment();
            }
        }).start();
    }    
    
    // Rep
    private BigInteger number = BigInteger.ZERO;
    private Set<NumberListener> listeners = new HashSet<>();
    
    // Abstraction function
    //    AF(number, listeners) = a counter currently at 'number'
    //        that sends events to the 'listeners' whenever it changes
    // Rep invariant
    //    true
    // Thread safety argument
    //    uses the monitor pattern -- the rep is guarded by this object's lock,
    //    acquired on entering every public method
    
    /** Make a counter initially set to zero. */
    public Counter() {
    }
    
    /** @return the value of this counter. */
    public synchronized BigInteger number() {
        return number;
    }
    
    /** Increment this counter. */
    public synchronized void increment() {
        number = number.add(BigInteger.ONE);
        callListeners();
    }

    /** Modifies this counter by adding a listener.
      * @param listener called by this counter when it changes. */
    public synchronized void addNumberListener(NumberListener listener) {
        listeners.add(listener);
    }

    /** Modifies this counter by removing a listener.
      * @param listener will no longer be called by this counter. */
    public synchronized void removeNumberListener(NumberListener listener) {
        listeners.remove(listener);
    }

    // Call all listeners to notify them about a new number
    private void callListeners() {
        // iterate over a copy of listeners so that we are re-entrant
        // (i.e. a listener may call add/removeNumberListener,
        // which would mutate the set in the midst of iteration)
        for (NumberListener listener : Set.copyOf(listeners)) {
            listener.numberReached(number);
        }
    }
    
}