0

I am trying to basically 1) create random DFAs and 2) show them one after another using JFrame. I have worked out how to create DFAs randomly, let GraphViz take care of visualizing them and save them in a .png file, and then display them in a JFrame. The DFAs are created within a loop, in which a function displayAutomaton is called, which takes care both of creating the .png, and of displaying it.

for(int i = 2; i < 10; i++) {
        AutomatonConverter.displayAutomaton(createRandomDFA(i));
        while(AutomatonConverter.jFrameExists) System.out.println("this works");
    }

The code of displayAutomaton is the following:

public static void displayAutomaton(Automaton A) {
    // Create the automaton-dependent file name
    String fileName = getAutomatonName(A);
    // Convert the Automaton and save it in a .png
    convertAutomaton(A, fileName);
    
    
    JFrame frame = new JFrame();
    // This static variable is used to determine whether the window
    // has been closed, so that the next iteration in the loop can start
    jFrameExists = true;
    ImageIcon icon = new ImageIcon(fileName);
    JLabel label = new JLabel(icon);
    frame.add(label);
    frame.addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
            jFrameExists = false;
            frame.dispose();
        }
    });
    frame.pack();
    frame.setVisible(true);
}

As you can see, I use a static variable jFrameExists in my main loop to infer if the window has been closed, because I would like to open the different images of the automatons consecutively. To this end, I've overridden the frame's Window-Listener. Here's the weird part: as you can see above, after displaying the automaton, a while-loop asks if jFrameExists has been set to false (which happens, again, when the window is closed). And currently, all this works. Each created automaton is shown in a differet JFrame, one after another, and each new window only opens after I close the previous one, which is exactly what I want - but I obviously don't want to print "this works" every time, so I tried to remove this simple instruction. However, then the code does not work anymore - this way, only the first image is shown, and after I close it, nothing happens.

I have tried simply ending the line there, asserting true (while(AutomatonConverter.jFrameExists) assert true;), and creating empty void methods to put here (while(AutomatonConverter.jFrameExists) doNothing();), but nothing works correctly - again, this way only the first image is shown, and the loop does not seem to continue to the next automaton. I just can't figure out why. Anybody have any idea?

Edit: Here's a generalized version of my code that is hopefully more understandable (ignore the code in convertAutomaton and createRandomDFA, I just added that for completeness' sake):

    public class main {
    static boolean jFrameExists = false;

    public static void convertAutomaton(Automaton A, String fileName) {
        // Creates a png of the automaton A and saves it under the fileName
        GraphViz gv = new GraphViz();
    gv.addln(gv.start_graph());
    gv.addln("rankdir=LR;");
    String startName;
    String shape;
    
    if(A instanceof DFA) {
        
        // Create the starting node
        State q0 = ((DFA) A).getQ0();
        startName = "\"" + q0.getName() + "start\"";
        shape = A.getF().contains(q0) ? "doublecircle" : "circle";
        // Add the node itself...
        gv.addln("node [shape = " + shape + "]; \"" + q0.getName() +"\";");
        // ... and its corresponding entry arrow
        gv.addln("node [shape = point]; " + startName + ";");
        gv.addln(startName + " -> \"" + q0.getName() + "\"");
        
        // Create all accepting nodes
        for(State s : A.getF()) {
            if(!((DFA) A).getQ0().equals(s)) gv.addln("node [shape = doublecircle]; \"" + s.getName() +"\";");
        }
        
    } else {
        
        // Create all starting nodes
        for(State s : ((NFA) A).getQ0()) {
            startName = "\"" + s.getName() + "start\"";
            shape = A.getF().contains(s) ? "doublecircle" : "circle";
            // Add the node itself...
            gv.addln("node [shape = " + shape + "]; \"" + s.getName() +"\";");
            // ... and its corresponding entry arrow
            gv.addln("node [shape = point]; " + startName + ";");
            gv.addln(startName + " -> \"" + s.getName() + "\"");
        }
        
        
        // Create all accepting nodes
        for(State s : A.getF()) {
            if(!((NFA) A).getQ0().contains(s)) gv.addln("node [shape = doublecircle]; \"" + s.getName() +"\";");
        }
    }
    
    gv.addln("node [shape = circle];");
    
    // Create all transitions
    ArrayList<State> reachable;
    String sName, s2Name;
    for(State s : A.getStates()) {
        for(char c : Automaton.alphabet) {
            sName = "\"" + s.getName() + "\"";
            reachable = s.delta(c);
            for(State s2 : reachable) {
                s2Name = "\"" + s2.getName() + "\"";
                gv.addln(sName + " -> " + s2Name + " [ label = \"" + c + "\" ];");
            }
        }
    }
    
    gv.add(gv.end_graph());
    File out = new File(fileName);
    gv.writeGraphToFile(gv.getGraph(gv.getDotSource(), type), out );
}
    }

    public static void displayAutomaton(Automaton A) {
        String fileName = "automaton.png"
        convertAutomaton(A, fileName);
        JFrame frame = new JFrame();
        jFrameExists = true;
        ImageIcon icon = new ImageIcon(fileName);
        JLabel label = new JLabel(icon);
        frame.add(label);
        frame.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    jFrameExists = false;
                    frame.dispose();
            }
            });
        frame.pack();
        frame.setVisible(true);
    }

    public static Automaton createRandomDFA(int numberOfStates) {
       // Create the states
    ArrayList<State> states = new ArrayList<State>();
    for(int i = 0; i < length; i++) states.add(new State(Integer.toString(i)));
    
    // Create random transitions
    Random r = new Random();
    State rState;
    for(State s : states) for(char c : Automaton.alphabet) {
        rState = states.get(r.nextInt(length));
        s.addTransition(c, rState);
    }
    
    // Create random accepting states
    ArrayList<State> F = new ArrayList<State>();
    for(State s : states) {
        if(r.nextInt(length) == length - 1) F.add(s);
    }
    if(F.size() == 0) F.add(states.get(r.nextInt(length)));
    
    // Create random starting state
    State q0 = states.get(r.nextInt(length));
    
    return new DFA(states, q0, F);
}
    }

    public static void main(String[] args) {
        
        for(int i = 2; i < 10; i++) {
            displayAutomaton(createRandomDFA(i));
            while(jFrameExists) System.out.println("waiting until the window is closed");
        }
    }
}

That's basically it. I know it's weird to not implement all the visualization stuff in its own class and to simply close the window instead of adding a button handling the functionality I want, but I essentially don't want to visualize anything in the long run, this was just supposed to test if the Automatons are created correctly. I will try different approaches that are more coherent with coding standards in the future, but I would nonetheless really like to know why calling System.out.println() makes a difference.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 2
    Please read "How to create a [mcve]". Then use the [edit] link to improve your question (do not add more information via comments). Otherwise we are not able to answer your question and help you. Meaning: you are only showing us **parts** of your code, others you **explain** to us. So we have to rely on your words and understand for that code. But thing is: if your understanding of that code would be fully correct ... you probably wouldn't have a problem, right. So maybe something about the things you "told us" doesnt hold up in reality. Long story short: dont tell us, show code ;-) – GhostCat Jun 09 '21 at 13:25
  • 2
    Also note: the whole approach sounds fishy. Using a single static variable to control such complex flows sounds like a strange starting point. Wouldnt it be much better to display those things within the same JFrame, but have some content pane within that frame update itself? – GhostCat Jun 09 '21 at 13:27
  • 1
    Please show us more info, because we have no idea what `AutomatonConverter` is, and what `AutomatonConverter.jFrameExists` does (I highly doubt that it targets the inner JFrame in your displayAutomaton class). You could, try using `java.awt.EventQueue.invokeLater(...` to create and display the JFrame as shown here to avoid any swing/thread issues: https://stackoverflow.com/a/22534931 – sorifiend Jun 09 '21 at 13:27
  • 1
    *and each new window only opens after I close the previous one,* why do you keep opening and closing frames? I would find that annoying as a user. Just have a single JFrame with a "Next" button. The next button would simply get the next image and use the `setIcon(...)` method of your JLabel to update the image. – camickr Jun 09 '21 at 13:36
  • @GhostCat Thanks for the time, I have edited my question to add the relevant info / address your thoughts. –  Jun 09 '21 at 16:32
  • Unrelated: learn about java naming conventions ... variables go camelCase ... that is why you should absolutely avoid single char names for anything (besides loop indexes). Use names that mean something, `A` means nothing, and can easily be misread because Uppercase. – GhostCat Jun 10 '21 at 04:57

0 Answers0