Search code examples
javaswingloggingjtextarea

return an ArrayList to swing with logger interface


I'm using the code in this answer to provide logging functionality, and it works well! Thanks gustafc.

Sending messages to a swing JTextArea from different places

I want to use this approach and add the function: writeEntry(String, String) to write to an ArrayList that I can hopefully access from swing; i.e, click 'show' and have it write this list of word pairs to a JText element.

so adding a bit to gustafc's code.

the interface:

package com.example.logging;
public interface SimpleActivityLogger {
    void logAction(String message);
    void writeEntry(String s1); //*** I added this ***
}

passing it to another class as per gustafc's example:

public class SimpleComponentLogger implements SimpleActivityLogger{
    private JTextComponent target;
    private ArrayList data;

    public SimpleComponentLogger(JTextComponent target, ArrayList data){
        this.target = target;
        this.data = data;
    }

    public void logAction(final String message){
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                target.setText(String.format("%s%s%n", new Object[]{target.getText(), message}));
            }
        });
    }

    public void writeEntry(String s1){
        data.add(s1);
        System.out.println("data array length: " + data.size());
    }
}

and then making an implementation:

// this originally extended NotesThread, 
// but I assume you won't have that on your system
public class LookupIterator3 extends Thread {
private SimpleActivityLogger logger;

// Constructor that passes logger instance
public LookupIterator3(SimpleActivityLogger logger){
    this.logger = logger;
}

public void doLookup(){
    this.start();
}

public void run()  {
    String[] words = {"the", "quick", "smart", "fox", "jumps", "over", "the", "lazy", "dog"};
    for(int i=0; i<words.length; i++){
        synchronized(words){
        try{
            logger.logAction(words[i]);
            words.wait(500);
        }
        catch(InterruptedException ie){ie.printStackTrace();}
        }
        logger.writeEntry(words[i]);
    }
}
}

however, when I try to access the ArrayList from swing, it comes back size()=0. Here's a big chunk of swing, but you should be able to just copy and paste it:

public class MySwingTest extends JFrame {
    private static final long serialVersionUID = 1L;
    private JTextField filename = new JTextField();
    private JTextField dir = new JTextField();

    private JTextPane output, answersPane;
    private JScrollPane scroller;
    private SimpleComponentLogger logger;
    private ArrayList answer;

    public MySwingTest() {
  JMenu fileMenu;
  JMenuBar menuBar;
  JMenuItem menuOpen, menuExit;

  JButton answerButton = new JButton("show answers");
  answerButton.addActionListener(new MyListener());

  menuBar = new JMenuBar();
  fileMenu = new JMenu("File");
  menuBar.add(fileMenu);
  menuOpen = new JMenuItem("open file");
  menuExit = new JMenuItem("exit");

  answer = new ArrayList();
  output = new JTextPane();
  logger = new SimpleComponentLogger(output, answer);
  scroller = new JScrollPane(output, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 
                  JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
  output.setEditable(false);
  answer = new ArrayList();
  answersPane = new JTextPane();
  answersPane.setSize(100, 200);
  answersPane.setEditable(false);

  fileMenu.add(menuOpen);
  fileMenu.add(menuExit);

  menuOpen.addActionListener(new MyListener());
  menuExit.addActionListener(new MyListener());

  JPanel p = new JPanel();
  p.setLayout(new GridLayout(2, 1));
  p.add(filename);
  p.add(dir);

  Container cp = getContentPane();
  cp.add(menuBar,BorderLayout.NORTH);
  cp.add(p);
  cp.add(scroller, BorderLayout.CENTER);
  cp.add(answersPane, BorderLayout.WEST);
  cp.add(answerButton, BorderLayout.SOUTH);

  dir.setEditable(false);
    filename.setEditable(false);
}

// Inner class listener  
class MyListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
            String eventCommand = e.getActionCommand();
            if(eventCommand.equals("exit")){
                System.exit(0);
            }
            if(eventCommand.equals("show answers")){
                String entry = (String)answer.get(0);
                answersPane.setText(entry);         
            }
            else {
                LookupIterator3 lu3 = new LookupIterator3(logger);
                lu3.doLookup();
            }
        }
    }

    public static void main(String[] args) {
        run(new MySwingTest(), 450, 600);
    }

    public static void run(JFrame frame, int width, int height) {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(width, height);
        frame.setVisible(true);
    }
}

Sorry about the long question, but this is doing my head in! Any suggestions? (I know I'm going to get ragged for writing a long qn, but I don't know what I could have left out)

OK. long edit complete. Open that baby up and select "open file" from the menu. It will run without a filechooser.


Solution

  • The problem is actually quite simple: in your MySwingTest constructor you have twice the line :

    answer = new ArrayList();
    

    once before you create your logger and once after. Simply drop the second call and it should work fine.

    Note: it is really a bad practice to share ArrayList between your instances (responsability is spread over several classes). It would be a lot simpler if you only had your logger creating directly its own ArrayList and providing access to it with a getter. Even better, it could directly offer an access to the objects it contains (preventing the possibility for other instances to modify the content of the ArrayList).

    Note 2: consider using interface-declaration over class-declaration: use List instead of ArrayList in order to reduce coupling.

    Note 3: I would strongly encourage you tu type your collections: List<String> answer = new ArrayList<String>();