Sorcerer's Cave
Strategy Notes

By Nicholas Duchon
May 8, 2015

Outline:

  1. Strategy
  2. Start
  3. Header Comments
  4. main
  5. Constructor
  6. JFrame
  7. JTextArea, JScrollPane
  8. JButton
  9. More JButton's
  10. Show those buttons!
  11. JTextField, JComboBox
  12. ActionListener
  13. Application Classes
  14. toSting and constructor methods
  15. Java 8 Listener Syntax
  16. JTree for the cave
Cave Project

Returns:

0: Strategy

Now that you have thought about Project 1 for the Sorcerer's Cave, I think you might be interested in how I would approach this project.

First, I would read over the requirements and digest them into a list. In fact, from now on, just about everything I do will be guided by lists - requirements, things to do, things to check, things I am pretty sure I have done, even things I have learned.

So let's start off - the basic requirements are:

  1. Create a program using a GUI
  2. Create an internal data structure
  3. Read a data file
  4. Present the internal structure in a GUI
  5. Search the data structure
  6. Various documentation elements

Next I will give some kind outline of the flow of the program:

  1. Read a data file and create the internal data structure
  2. Present the data structure
  3. Interact with the user allowing various searches
  4. End.

You will note that the overall flow of this program is actually pretty trivial, and most of you probably did this in your head. This would be fine, except that this project will get to be long, and one can and does so easily become distracted - either trying to remember stuff, worrying about losing stuff, or just not paying attention to the specific task one is currently trying to accomplish.

Thus, writing stuff down is a way to foster concentration on the task at hand.

So now I would ask about resources I have been given. In this case, we have the text, and from the instructor:

  1. A jar file that will generate data files, and the source code for that application.
  2. Two sample data files.
  3. Extra commentary about the desired internal data structure.
  4. A bunch of stuff about the format and content of the documentation.
  5. A Musings section, which looks like lots of sample Java code.
  6. A Musings page, pretty similar.
  7. Questions and answers in the Work Conference.
  8. Sample GUI displays in the Musings Conference.

My general approach:

  1. Think about the problem.
  2. Concentrate on the output first.

Why 2? Well, no matter what your program is supposed to do, if you cannot see the output, who knows what is going on internally?

So, let's start with the output. In this case, we are asked to create a GUI, and we have some specifications about what it should do, and we have some examples of what the display might look like. So, lists - let's make a list of the operations the project requires and the GUI elements we will need to support those features.

Feature Operation Supporting Java Class
Get the data file Select and Read JFileChooser, JButton
Display the data structure read
display
Scanner, File
JButton,  JTextArea, JScrollPane
Search target input field JTextField
Select field select what kind of thing to search for JComboBox?
Do search search the data structure JButton
Labels label the various controls JLabel


Questions? This would be a good place to stop and see if there are any questions about the project that would be worth asking. For example:

  1. Should the program check for errors in the data file? If so, what should the program do?
  2. Should the program check for errors in user input? If so, what should the program do?
  3. What about items that don't have parents?
  4. Resizing? How should the GUI resize?

As with any larger project, there are many tasks, many ways to proceed, but we should pick ONE and stick to it as long as we can. And the key is in any case, code a LITTLE, test a LOT!

Here are the main choices:

  1. Select a class that will have main, and start there.
  2. Work on the GUI - making it look right.
  3. Connecting the GUI actions to stubs
  4. Filling in the stubs.
  5. Design the class hierarchy - mostly just the class names and their extends relationships at first.
  6. Try to select and read the data file.

So, we should generally pick the easiest first! In this case, we will need a main no matter what, and we will need a class to hold it. What should we call it? After thinking about what is going on for a little while, something like Cave or SorcerersCave seems appropriate. Note that Project1 may not be a good choice since we know that the rest of the projects in this class are going to start with this one and add stuff as we go along.

1: Start

So, here's the code that I would start with:

public class SorcerersCave {
} // end class SorcerersCave

 You will note a few interesting features of this code:

  1. It will compile correctly if the file name is correct.
  2. It doesn't really do anything at all.
  3. I have put a comment on the right brace to indicate that that brace is the end of this particular class, this will help a lot when I wonder if a method is in this class or some other class, or what is supposed to be closed by this brace.

2: Header Comments

Next, I will add basic comments at the start of the class - I will do this in all files as a matter of course.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

public class SorcerersCave {
} // end class SorcerersCave

3: main

main next? Sure. In this case, main can be pretty trivial, and I have chosen to do all the initialization of the program in the constructor, which is to come. Thus, main:

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

public class SorcerersCave {
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

4: Constructor

This code will compile and run, though it is pretty trivial, and actually running this code will not produce any output at all. If you are concerned about that, you should include a print statement, as I will in the next step, where I introduce the constructor. Since main is so trivial in this program, I will leave it at the end of the file. I suggest that it should be either at the end of a file or at the start since main is such a special method.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

public class SorcerersCave {

    public SorcerersCave () {
        System.out.println ("In constructor");
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

You will note that this code has some actual output, and that it is nearly inconceivable that this code will have to be changed ever, which is the point in this approach - almost everything we do should simply add to code already working. Also, this code correctly calls the constructor!

Now we come to a fork in the road - which of the following should we focus on next? In particular, we should focus our attention on ONE of the following, NOT all at once:

  1. More classes?
  2. The GUI?
  3. Reading the file?

Here's my thinking, since no matter what we do, we will want to see output as quickly as possible, and we really need to rely on the output being correct, we would be best served by going for the GUI.

Having made this decision, what next? Well, we either already know a really good a quick way to create a GUI, or we should look at examples and adapt one or more of them to our purposes. We have places to look - the text, and the Musings. Since the professor has provided us with examples, perhaps we should start by seeing what is there so we have some idea what he/she likes to see, and perhaps get some insights into approaches other than the text's. Looking at the Musings page - we find an entry labelled JTextArea with scrolling - sure sounds like the kind of thing we are looking for, doesn't it? Let's look.

5: JFrame

Let's start by creating a JFrame - and we see after a little thought that one way to go is to let our class extend JFrame. That way, we simplify our code. There are other possibilities, but this seems easy. Also, we see that we need to import javax.swing.JFrame. Here's the code. Note that if we use -Xlint, the compiler tells us that it really wants to see a value for serialVersionUID. At the moment it really doesn't matter what value we use, as long as we give a value, so here I have chosen 123.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import javax.swing.JFrame;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

Hmmm - this code will compile, but when we run it, nothing happens. Obviously, we must tell the JVM more about the JFrame. Let's look over the example and see what we might be missing.

Perhaps setVisible? Try this and you can see that something happened, but it looks pretty small. You will note that just closing this window will not end the program.

How about setSize? Ah - now we see a window, with the label we want. Still, closing the window does not end the program.

How about setDefaultCloseOperation? Now closing the frame ends the program!

We have made serious progress.

6: JTextArea, JScrollPane

Now let's try for two steps at once - a scrolling text area, since that is what the example promises for us. Looking at the code, we see that the JTextArea is a class instance variable, while the JScrollPane is a local variable in the constructor. Why? Well, we should think about this. After some thought, you may come to the conclusion that this program and our program will not need to reference the scroll pane any more, but will need to access the text area many times over the course of running the program, so this choice of local and class variables seems to make sense. This might be a good place to add some comments to our code since we thought about this detail and have come to a reason for doing things this way - and we might find some reason later on to change our minds. Note that we will need to add some import statements now also.

We have also chosen to add the scroll pane into the center of the JFrame. Recall that JFrame uses BorderLayout as the default layout, so this is going to work out nicely.

So, here's the code so far:

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (300, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

Hmmm - the text area may only show up after I have resized the display. This is annoying and I should fix this shortly. After a bit of research, it turns out that if I use the command validate () after the page is set up, it will work nicely. See the code in topic 8:.

Now we can do some interesting testing. First, we can just type into this text area, so type some stuff, it doesn't matter what. You can then copy and paste more of the same text and see how the display handles extensive data. You can type lines and see how the display reacts to many lines. You can now resize the display and see how that works also.

Note that scroll bars show up as needed, but the text is not wrapped. This looks pretty close to how we want the display to work. Also notice that the text area fills up the entire display, which will be nice when we want to display lots of data.

7: JButton

So now let's add some GUI controls and labels. We can add them to the top, or the bottom or around. These are the natural choices when using the BorderLayout. I will add them to the top. At this point, I just want to see that I can add them to get them to look about right. Connecting the controls to actions will come later. I want to add some JButton's, some JLabel's and a JTextField.

Perhaps I should start with just one JButton and see how that works out before I go add a lot of code that might not work out as I hoped.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (300, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jba = new JButton ("Read");
        
        add (jba, BorderLayout.PAGE_START);
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

Hmmm - the button only shows up after I have resized the display. This is annoying and I should fix this NOW. After a bit of research, it turns out that if I use the command validate () after the page is set up, it will work nicely. 

8: More JButton's

Now, let's try adding more buttons and see what happens.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (300, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jbr = new JButton ("Read");
        JButton jbs = new JButton ("Search");
        
        add (jbr, BorderLayout.PAGE_START);
        
        validate ();
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

9: Show those buttons!

Hmmm - the second button didn't show up. What should I do? Well, looking at the sample code, we see that the buttons are not added directly to the display, but are added to a JPanel, which is then added as a single item to the display. Let's try that:

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JPanel;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (300, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jbr = new JButton ("Read");
        JButton jbs = new JButton ("Search");
        
        JPanel jp = new JPanel ();
        jp.add (jbr);
        jp.add (jbs);
        
        add (jp, BorderLayout.PAGE_START);
        
        validate ();
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

WEEE! This worked, and we see two buttons. Note that this is using FlowLayout, and that if we shrink the screen too much, the buttons disappear.

10: JTextField, JComboBox

Of the stuff we want on the top, we now have two buttons. We certainly want at least one label, one text field, and a combo box, but we may also want another button to redisplay the data set.

Including all these features gives us the following code. I have made with the initial width 600 so we can see all the controls on the top.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JComboBox;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (600, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jbr = new JButton ("Read");
        JButton jbd = new JButton ("Display");
        JButton jbs = new JButton ("Search");
        
        JLabel jls = new JLabel ("Search target");
        
        JTextField jtf = new JTextField (10);
        
        JComboBox <String> jcb = new JComboBox <String> ();
        jcb.addItem ("Index");
        jcb.addItem ("Type");
        jcb.addItem ("Name");
        
        JPanel jp = new JPanel ();
        jp.add (jbr);
        jp.add (jbd);
        jp.add (jls);
        jp.add (jtf);
        jp.add (jcb);
        jp.add (jbs);
        
        add (jp, BorderLayout.PAGE_START);
        
        validate ();
    } // end no-parameter constructor
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

We now have a GUI that looks ok!

Next: actions? classes? reading the file?

11: ActionListener

Actually, the only way to see anything is to get at least one of the buttons to be active. And considering how the program is supposed to work, we should try to get the read button working first. Next question - how? Well, add an action listener to this button. Now for another question - how should we make this work. It turns out that one nice way to do it is to use an anonymous inner class that immediately calls an instance method, as follows, where I have done this for all the buttons. Note that you really should do this one button at a time!

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JComboBox;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();
    JComboBox <String> jcb;
    JTextField jtf;

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (600, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jbr = new JButton ("Read");
        JButton jbd = new JButton ("Display");
        JButton jbs = new JButton ("Search");
        
        JLabel jls = new JLabel ("Search target");
        
        jtf = new JTextField (10);
        
        jcb = new JComboBox <String> ();
        jcb.addItem ("Index");
        jcb.addItem ("Type");
        jcb.addItem ("Name");
        
        JPanel jp = new JPanel ();
        jp.add (jbr);
        jp.add (jbd);
        jp.add (jls);
        jp.add (jtf);
        jp.add (jcb);
        jp.add (jbs);
        
        add (jp, BorderLayout.PAGE_START);
        
        validate ();
        
        jbr.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    readFile ();
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
        
        jbd.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    displayCave ();
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
        
        jbs.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    search ((String)(jcb.getSelectedItem()), jtf.getText());
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
    } // end no-parameter constructor
    
    public void readFile () {
        jta.append ("Read File button pressed\n");
    } // end method readFile
    
    public void displayCave () {
        jta.append ("Display Cav button pressed\n");
    } // end method readFile
    
    public void search (String type, String target) {
        jta.append (String.format ("Search button pressed, type: >%s<, target: >%s<\n", type, target));
    } // end method readFile
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

Now all the buttons are active and connect to code methods. Also, we have confirmed that each button goes to the right method, that the combo box works, and the the information in the combo box and the text field are read correctly. We should never have to question these issues again!

12: Application Classes

It is now time to make a design decision: should we have the SorcerersCave we have defined also work directly with the required data structures, starting with ArrayList <Party>, or should we define a new class, say Cave, and put the top level ArrayList in that class. In the first approach, the SorcerersCave will get rather long and be doing both code related to the GUI and code related to the inner workings of the program. On the other hand, if we add another class, Cave, we mostly just get code split into two conceptually different places. Note that we have no guidance on this point in any of the requirements specifications, other than some vague note about the GUI being separate. Perhaps that is enough to justify the Cave class separated from the GUI stuff.

So let's go with that. Also, as we consider the various classes this project needs, it looks like it might pay to have an umbrella class for the required classes, which leads us to the following class structure:

We can now address the structural relationships among these classes (NOT extends):

Each class will have other instance variables (attributes), but those attributes will not affect any of the other classes, at least so far.

These considerations lead us to the following class structure:

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JComboBox;

import java.util.ArrayList;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();
    JComboBox <String> jcb;
    JTextField jtf;
    Cave cave = new Cave ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (600, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jbr = new JButton ("Read");
        JButton jbd = new JButton ("Display");
        JButton jbs = new JButton ("Search");
        
        JLabel jls = new JLabel ("Search target");
        
        jtf = new JTextField (10);
        
        jcb = new JComboBox <String> ();
        jcb.addItem ("Index");
        jcb.addItem ("Type");
        jcb.addItem ("Name");
        
        JPanel jp = new JPanel ();
        jp.add (jbr);
        jp.add (jbd);
        jp.add (jls);
        jp.add (jtf);
        jp.add (jcb);
        jp.add (jbs);
        
        add (jp, BorderLayout.PAGE_START);
        
        validate ();
        
        jbr.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    readFile ();
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
        
        jbd.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    displayCave ();
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
        
        jbs.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    search ((String)(jcb.getSelectedItem()), jtf.getText());
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
    } // end no-parameter constructor
    
    public void readFile () {
        jta.append ("Read File button pressed\n");
    } // end method readFile
    
    public void displayCave () {
        jta.append ("Display Cav button pressed\n");
    } // end method readFile
    
    public void search (String type, String target) {
        jta.append (String.format ("Search button pressed, type: >%s<, target: >%s<\n", type, target));
    } // end method readFile
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

class Cave {
    ArrayList <Party> parties = new ArrayList <Party> ();
    ArrayList <CaveElement> stuff = new ArrayList <CaveElement> ();
} // end class Cave

class CaveElement {
} // end class CaveElement

class Party extends CaveElement {
    ArrayList <Creature> members = new ArrayList <Creature> ();
} // end class CaveElement

class Creature extends CaveElement {
    ArrayList <Treasure> treasures = new ArrayList <Treasure> ();
    ArrayList <Artifact> artifacts = new ArrayList <Artifact> ();
} // end class Creature

class Artifact extends CaveElement {
} // end class Artifact

class Treasure extends CaveElement {
} // end class Treasure

This will compile, even if it doesn't do anything interesting.

13: toString and constructor methods

So what next? We could think about reading the file, creating the data structure, searching, or other issues. BUT, I suggest that we really need to make sure we can view whatever structure we create, so I would go for getting the display button to work. And we need something to display, so we will need to create some trivial structure somehow.

And now I am going to propose a strategic thought - a TARGET LINE OF CODE. Let me give an example, and perhaps you will consider this a good idea also.

jta.setText (cave.toString()); // in response to the display button
 

So simple. What it implies is that the Cave class should have a way of presenting itself nicely as a String, and if the display of that class should change, it will only mean changes within that class. This is the power of the toString method in Java.

Actually, we could even use slightly less code as follows:

jta.setText ("" + cave); // call toString as part of the type coercion
 

Annoying: if we just said jta.setText (cave), we would get the Object version of toString, which is not very helpful.

Now we should consider how to test the functioning of toString, and how it should work with the classes we have defined: CaveElement, Party, Creature, Treasure, and Artifact.

Having created the interacting structure, which I have called the multi-tree structure, of connections between instances using ArrayLists, we now turn our attention to the attributes.

Looking through the specifications for the data files, we see that all the entries start with a character, the next field is an index, and the next field is a name. After that, the various types of elements have different formats, but since all the elements have an index (int) field and a name (String) field, it makes sense to put those fields into the parent class of all those classes, namely the CaveElement class. And we can use that field in creating elements, linking them together and then checking to make sure the toString methods work nicely together.

Here's the code that does all that, along with a test case put together in the readFile method.

// File: SorcerersCave.java
// Date: Jun 21, 2013
// Author: Nicholas Duchon
// Purpose: demonstrate the development of a project - 
//    in this case, the Sorcerer's Cave project

import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JComboBox;

import java.util.ArrayList;

public class SorcerersCave extends JFrame {
    static final long serialVersionUID = 123L;
    
    JTextArea jta = new JTextArea ();
    JComboBox <String> jcb;
    JTextField jtf;
    Cave cave = new Cave ();

    public SorcerersCave () {
        System.out.println ("In constructor");
        setTitle ("Sorcerer's Cave");
        setSize (600, 300);
        setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setVisible (true);
        
        JScrollPane jsp = new JScrollPane (jta);
        add (jsp, BorderLayout.CENTER);
        
        JButton jbr = new JButton ("Read");
        JButton jbd = new JButton ("Display");
        JButton jbs = new JButton ("Search");
        
        JLabel jls = new JLabel ("Search target");
        
        jtf = new JTextField (10);
        
        jcb = new JComboBox <String> ();
        jcb.addItem ("Index");
        jcb.addItem ("Type");
        jcb.addItem ("Name");
        
        JPanel jp = new JPanel ();
        jp.add (jbr);
        jp.add (jbd);
        jp.add (jls);
        jp.add (jtf);
        jp.add (jcb);
        jp.add (jbs);
        
        add (jp, BorderLayout.PAGE_START);
        
        validate ();
        
        jbr.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    readFile ();
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
        
        jbd.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    displayCave ();
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
        
        jbs.addActionListener ( new ActionListener () {
                public void actionPerformed (ActionEvent e) {
                    search ((String)(jcb.getSelectedItem()), jtf.getText());
                } // end required method
            } // end local definition of inner class
        ); // the anonymous inner class
    } // end no-parameter constructor
    
    public void readFile () {
        jta.append ("Read File button pressed\n");
        
        Party p; Creature c;
        cave.parties.add (p = new Party ("party A"));
        p.members.add (c = new Creature ("Creature CA"));
        c.artifacts.add (new Artifact ("art aa"));
        c.artifacts.add (new Artifact ("art ab"));
        c.artifacts.add (new Artifact ("art ac"));
        c.treasures.add (new Treasure ("trs ta"));
    } // end method readFile
    
    public void displayCave () {
        jta.setText ("Display Cav button pressed\n");
        jta.append ("" + cave);
    } // end method readFile
    
    public void search (String type, String target) {
        jta.append (String.format ("Search button pressed, type: >%s<, target: >%s<\n", type, target));
    } // end method readFile
    
    public static void main (String [] args) {
        SorcerersCave sc = new SorcerersCave ();
    } // end main
} // end class SorcerersCave

class Cave {
    ArrayList <Party> parties = new ArrayList <Party> ();
    ArrayList <CaveElement> stuff = new ArrayList <CaveElement> ();
    
    public String toString () {
        String st = "Cave.toString:\nThe Parties\n";
        for (Party p: parties) 
            st += p + "\n";
        st += "\n+++++++\nThe unassociated stuff:\n";
        for (CaveElement e: stuff)
            st += e + "\n";
        return st;
    } // end toString method
} // end class Cave

class CaveElement {
    String name = ""; // make sure we have a default value here, NOT null
    int index = 0;
    
    public String toString () {
        return "CaveElement: " + name;
    } // end method toString
} // end class CaveElement

class Party extends CaveElement {
    ArrayList <Creature> members = new ArrayList <Creature> ();
    
    public Party (String n) {name = n;}
    
    public String toString () {
        String st = "    Members:\n";
        for (Creature c: members) 
            st += "    " + c + "\n";
        return st;
    } // end method toString
} // end class CaveElement

class Creature extends CaveElement {
    ArrayList <Treasure> treasures = new ArrayList <Treasure> ();
    ArrayList <Artifact> artifacts = new ArrayList <Artifact> ();

    public Creature (String n) {name = n;}
    
    public String toString () {
        String st = "    " + name + "\n       Artifacts:\n";
        for (Artifact a: artifacts) 
            st += "        " + a + "\n";
        st += "       Treasures:\n";
        for (Treasure t: treasures) 
            st += "        " + t + "\n";
        return st;
    } // end method toString
} // end class Creature

class Artifact extends CaveElement {

    public Artifact (String n) {name = n;}
    
    public String toString () {
        return "          " + name;
    } // end method toString
} // end class Artifact

class Treasure extends CaveElement {

    public Treasure (String n) {name = n;}
    
    public String toString () {
        return "          " + name;
    } // end method toString
} // end class Treasure

14: Java 8 Listener Syntax

Java 8 has added support for a feature called lambda expressions, which, among other cool things, can reduce the size of the listeners a lot:

    jbr.addActionListener (e -> readFile());
    jbd.addActionListener (e -> displayCave ());
    jbs.addActionListener (e -> search ((String)(jcb.getSelectedItem()), jtf.getText()));
       
//         jbr.addActionListener ( new ActionListener () {
//                 public void actionPerformed (ActionEvent e) {
//                     readFile ();
//                 } // end required method
//             } // end local definition of inner class
//         ); // the anonymous inner class
//        
//         jbd.addActionListener ( new ActionListener () {
//                 public void actionPerformed (ActionEvent e) {
//                     displayCave ();
//                 } // end required method
//             } // end local definition of inner class
//         ); // the anonymous inner class
//        
//         jbs.addActionListener ( new ActionListener () {
//                 public void actionPerformed (ActionEvent e) {
//                     search ((String)(jcb.getSelectedItem()), jtf.getText());
//                 } // end required method
//             } // end local definition of inner class
//         ); // the anonymous inner class

15: JTree method

Here's a very simple way to create a JTree for the multi-tree structure defined above:

   public DefaultMutableTreeNode createNodes(String title) {
      DefaultMutableTreeNode top = new DefaultMutableTreeNode(title);
      DefaultMutableTreeNode pn, cn;
      for(Party p: cave.parties) {      
         pn = new DefaultMutableTreeNode(p.name);
         top.add(pn);             
         for(Creature c: p.members) {
            cn = new DefaultMutableTreeNode(c.name);
            pn.add(cn);
            for(Treasure t: c.treasures)
               cn.add(new DefaultMutableTreeNode("T: " + t.type));
            for(Artifact a: c.artifacts)
               cn.add(new DefaultMutableTreeNode("A: " + a.type));
         } // end for each creature
      } // end for each party
      return top;
   } // end method createNodes

You could call this method in a context like the following, adding the top node of the tree directly to a JTree which can then be added to a JPanel or a JScrollPane and then a JPanel:

    JTree tree = new JTree(createNodes("My Cave"));
    JScrollPane pane2 = new JScrollPane(tree);


Notice that in the createNodes method, the order of adding node to parent and children to node is not important.

It is probably a good idea to have a button that will expand or collapse all the nodes - which can be quite tedious if the one has to press every twister.

I hope you have found this presentation helpful - please let me know what you think of it.

And I hope that you can proceed developing the project further in this same spirit. Let me know if you would like me to continue.