Skip to main content
Object Oriented Software Design
Object Oriented Software Design

Lab #5: Packages, Serialization, and a Swing GUI

The final lab on the circuit simulator: organize it into real packages, make it persistable, and put a small graphical front end on top of it.

Objectives

  • Split a project into packages, and feel the compiler’s package-visibility rules push back when access modifiers are wrong.
  • Deliberately break and repair visibility to see exactly which modifier controls which kind of access.
  • Persist and reload an object graph with serialization.
  • Wire an existing domain model into a provided (partial) Swing GUI.

1. Packages and encapsulation

Difficulty: Rx
  1. Move every class except your test class into a circuits package:
    • create a circuits directory and move the relevant .java files into it,
    • add package circuits; as the very first line of each of those files (before any import),
    • package directories must be reachable via CLASSPATH or the -cp flag to javac/java — the current directory (.) is included by default, so compiling from above the circuits directory is usually enough: javac ./circuits/*.java.
  2. Move your test class into its own, separate test package:
    • create a test directory (a sibling of circuits) and move your CircuitTest there, with package test; at the top,
    • import the circuits package’s classes: import circuits.*;,
    • compile with javac ./test/CircuitTest.java.
  3. At this point compilation will very likely fail on visibility grounds. Typical errors look like:
    test/CircuitTest.java:23: circuits.Circuit is not public in circuits;
     cannot be accessed from outside package
    or, similarly, a method reported as not public when the caller sits outside the package. Fix these by making whatever circuits needs to export — for test.CircuitTest to use it — explicitly public, and leaving everything else unmarked or protected. As a rule of thumb:
    • classes: public if used from outside the package, otherwise unmarked (“package-private”),
    • methods: public if part of the exported protocol, otherwise unmarked or protected (“subclass-limited”),
    • instance fields: unmarked, protected, or private — keeping in mind that private fields aren’t visible even to subclasses in the same package.
  4. Run it: java -cp . test.CircuitTest (adjust the classpath if needed).
  5. Now deliberately experiment with visibility, to see the rules from both directions:
    • Remove public from the Circuit class, recompile circuits, then recompile CircuitTest — you should hit the “not public” class error from step 3. Restore it, then do the same for one exported method (e.g. getSwitches()), and observe the analogous method-level error.
    • TwoInputGate is an internal factoring class inside circuits, never referenced from test. Make it non-public and recompile both packages — does anything break? Do the same check for ProbeTable, an internal helper for the probe/unprobe machinery — confirm it, too, can safely stay package-private.
    • Make TwoInputGate’s in1/in2 fields private and recompile circuits alone. What happens to its subclasses, And and Or, which read those fields directly?
    • Circuit’s components list should be encapsulated (anything but public — at least protected, since Circuit itself is responsible for keeping it sorted and consistent). Try, from CircuitTest, reaching into it directly and corrupting it — e.g. circuit.components.add(new And()) — and see what the compiler says. Then temporarily make the field public, repeat the same line, and run your test again: notice that partList() — and everything that depends on the list staying sorted — is now silently wrong.
  6. Before moving on, double-check every modifier is back to its correct, final value: Circuit public, getSwitches() public, TwoInputGate and ProbeTable non-public, TwoInputGate’s in1/in2 non-private, components protected (not public). Sections 2 and 3 below build directly on this codebase and assume it compiles cleanly with correct encapsulation — carrying over any of the deliberately-broken states from step 5 will cause confusing failures later that have nothing to do with serialization or the GUI.

2. Saving circuits with serialization

Difficulty: Rx

Serialization will let you save a circuit’s configuration to a file and reload it later. Add a pair of methods to Circuit:

  • public void save(String fileName) throws IOException — writes the circuit’s name (a String, already serializable) and its components list to fileName. This requires the Component class to implement Serializable — every subclass inherits that automatically, so you only need the implements Serializable clause on Component itself, not repeated on Switch, Gate, And, and so on.
  • public void load(String fileName) throws IOException, ClassNotFoundException — the inverse: reads the name and the components list back from the file.

Both methods should simply declare their checked exceptions with throws and let them propagate — no need to catch anything inside Circuit itself; the calling code (below) will handle it.

Test it

  • From CircuitTest, save your example circuit’s state to a file (ask the user for a filename, e.g. circuit.bin).
  • Write a second, separate test class, SerializationTest, whose main takes a saved filename as an argument, builds a circuit from it, and calls traceStates() on it to confirm the reloaded configuration matches:
    Circuit circuit = new Circuit();
    circuit.load(fileName);
    circuit.traceStates();

3. A graphical front end

Difficulty: Hard

The last piece: a small Swing application, Tester, that loads a previously-saved circuit and lets you interact with it graphically. From top to bottom, the window shows:

  • an image of the circuit (a static picture, loaded from a .png file),
  • a non-editable text area with the circuit’s description,
  • a dropdown listing the circuit’s switches,
  • an on/off toggle for whichever switch is currently selected,
  • a text area showing switch states,
  • a text area showing valve (output) states.

The whole thing lives in a single JFrame, titled with the loaded circuit’s name, with its components stacked vertically (BoxLayout along the Y axis). The application takes a base filename as its argument (e.g. circuit, with no extension) and expects to find both circuit.bin (the serialized circuit) and circuit.png (its picture) alongside it.

You’ll be given a starter GUI to complete rather than writing Swing wiring from scratch:

  1. Download the provided GUI starter package, ihm.jar. It contains a circuit image and a partially-implemented Tester class in a gui package. Unpack it with jar xvf ihm.jar.
  2. Read through Tester and identify how it uses your circuits package. In particular, notice that displaying the circuit’s description requires two methods on Circuit you haven’t written yet: String getName() (returns the circuit’s name) and a String-returning variant of description() (rather than one that prints directly) — add both.
  3. Compile gui.Tester and run it against your previously-saved circuit: java gui.Tester circuit. At this point you should see the window described above, but incomplete: the on/off toggle doesn’t do anything yet, and the valve-state text area is missing entirely.
  4. Follow the guide comments in the provided code to:
    • add the missing text area for valve/output state,
    • make flipping the selected switch actually update both state text areas.
  5. Recompile and try it again — you should now have a fully interactive tester for your circuit.