Memra

Part 2 recap: reflection-built events & thread-safe concurrency

The advanced half: build Event subclasses by name with reflection (so new events drop in without recompiling the controller), make Event implement Runnable to run one thread per event, suspend/resume the thread collection cooperatively, and guard shared state with a synchronized setVariable.

Part 2: make the framework open-ended and concurrent

Part 1 still hard-references each event class (new LightOn(t)). Part 2 removes even that coupling with reflection, then runs every event on its own thread with proper synchronization.

### 1. Build events by name with reflection The parser already extracts the event-name string ("LightOn"). Instead of a giant if/else mapping names to constructors, Part 2 loads the class by name and constructs it reflectively (Module 7):

String fqn = "GreenhouseControls$" + name;        // inner class binary name
Class<?> c = Class.forName(fqn);
Constructor<?> ctor = c.getConstructor(GreenhouseControls.class, long.class);
Event e = (Event) ctor.newInstance(outer, time);

Note the inner-class detail: an inner class's constructor takes a hidden first parameter — the enclosing GreenhouseControls instance. Its binary name uses $ (GreenhouseControls$LightOn). With reflection in place, adding a new event type needs no controller recompile — drop in the class and name it in the file. The exam phrases this as *"how do you add an event class without changing/recompiling the controller?"* → reflection.

### 2. Event implements Runnable — one thread per event In Part 2 each event runs concurrently: Event is made implements Runnable, its old timing/action logic moves into run(), and the controller starts a thread per event (Module 10):

public abstract class Event implements Runnable {
    public void run() {
        // wait until ready, then perform the action
        action();
    }
}
// start it:
new Thread(event).start();

The canonical mistake: calling event.run() directly executes it on the *current* thread — no concurrency. You must call new Thread(event).start() to get a new thread.

### 3. Suspend / resume the running collection The control panel can pause and continue everything. The *deprecated* Thread.suspend()/resume() deadlock-prone APIs are avoided; instead each event watches a cooperative flag and uses a guarded block (wait/notifyAll, Module 10.4):

private volatile boolean paused = false;
public synchronized void pause()  { paused = true; }
public synchronized void unpause() { paused = false; notifyAll(); }
public synchronized void awaitResume() throws InterruptedException {
    while (paused) wait();          // guarded block: re-check in a loop
}

### 4. Synchronize the shared state Now that many threads can mutate greenhouse state at once, every write must be a critical section. The project funnels writes through one guarded mutator that updates a shared collection of TwoTuple name/value pairs (the generic TwoTuple<A,B> from Module 6):

private List<TwoTuple<String,Boolean>> state = new ArrayList<>();
public synchronized void setVariable(String name, boolean value) {
    state.add(new TwoTuple<String,Boolean>(name, value));
}

synchronized makes the read-modify-write of state atomic with respect to other synchronized callers — without it, two threads adding at once can corrupt the ArrayList or lose an update. This is the course's canonical race-on-shared-state lesson.

How it runs The parser reads each line, reflection builds the named Event, the controller starts a thread for it; threads perform their action()s concurrently, each routing state changes through the synchronized setVariable; the panel's Suspend/Resume flips the paused flag that every thread's guarded block honours.

NORMAL ~/memra/learn/comp-308/part-2-recap utf-8 LF