Search code examples
javasynchronize

Synchronize problems


Basically, I need my program to pause while I'm waiting for input from a JTextField which is in the main JFrame, alongside all other elements so it is not a popup. However, the way I am doing it at the moment it pauses the whole program as I am using while(true), or something similar.

Here is how my GUI looks so you have a better idea of what I am talking about;

img

as you can see the program gets stuck when I enter the command start and press enter.


This is the way I initialize my synchronize:

    input = new JTextField();
    inputScrollPane = new JScrollPane(input, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    input.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            if (waitingForInput) {
                synchronized (holder) {
                    holder.add(input.getText());
                    holder.notify();
                }
                waitingForInput = false;
            }

            JTextField sauce = (JTextField) e.getSource();
            appendCommand(sauce.getText());
            sauce.setText("");
        }
    });

and here is what happens when I call notify on the LinkedStack...

public String getInput(String s) {
    appendInput(s + ": ");
    input.requestFocus();

    synchronized (holder) {
        while (holder.isEmpty())
            //System.out.println("input inside 2"); //STOPS HERE
            try {
                holder.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        System.out.println("input inside 3");

        String nextString = holder.remove(0);

        System.out.println(nextString);
        return nextString;
    }

    /*Document document = getConsoleInput().getDocument();
    Element rootElem = document.getDefaultRootElement();
    int numLines = rootElem.getElementCount();
    Element lineElem = rootElem.getElement(numLines - 2);
    int lineStart = lineElem.getStartOffset();
    int lineEnd = lineElem.getEndOffset();
    try {
        System.out.println(document.getText(lineStart, lineEnd - lineStart));
        return document.getText(lineStart, lineEnd - lineStart);
    } catch (BadLocationException e) {
        e.printStackTrace();
        return "-1";
    } */           //another attempt I had
}

This is the hierarchy or chain of methods that get called, in order:

The start where I initialize the JTextField:

input = new JTextField();
    inputScrollPane = new JScrollPane(input, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    input.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            if (waitingForInput) {

                synchronized (holder) {
                    holder.add(input.getText());
                    holder.notify();
                }
                waitingForInput = false;
            }

            JTextField sauce = (JTextField) e.getSource();
            appendCommand(sauce.getText()); //<<<
            sauce.setText("");
        }
    });

AppendCommand(String s) method which is called inside the EDT

public void appendCommand(String s) {
    appendInput(s);
    if (!testIfValid(s)) return;
    handler = new CommandHandler(getCurrentInstance());
    switch (CommandList.valueOf(s.toUpperCase())) {
        case HELP:
            handler.help();
            break;
        case START:
            handler.start(currentLevelActive);  //<<<<<<<<<<<
            break;
        case STOP:

            break;
        case EXIT:

            break;
        //default:

    }
}

next :

  public void start(Level l) {
    level_instance = l;
    if (level_instance != null) {
        level_instance.start();
    } else {
        //CREATE new level
        int width = 0, height = 0;
        boolean go;

        console_instance.waitingForInput(true);
        console_instance.setInputMessage("Enter level width : ");
        InputThread widthThread = new InputThread("width", console_instance);

        while (!widthThread.done) {
        } ///STOPS HERE ETERNALY!!
        width = widthThread.getInputResult();

        ...

InputThread class:

class InputThread extends Thread {
Console console_instance;
String choice;
int inputResult;
boolean done;

public InputThread(String s, Console c) {
    console_instance = c;
    choice = s;
}

@Override
public void run() {
    String s = "";
    console_instance.setWaitingForInput(true);
    do {
        s = console_instance.getInput("Enter " + choice + " of the level"); // <<<<<<<<<<
    } while (!isInt(s));
    inputResult = Integer.parseInt(s);
    done = true;
}

public int getInputResult() {
    return inputResult;
}

private boolean isInt(String s) {
    int i;
    boolean b;
    try {
        i = Integer.parseInt(s);
        b = true;
        if (i < 0) {
            b = false;
        }
    } catch (NumberFormatException ne) {
        b = false;
    }
    return b;
}
}

Console.getInput(String s) method:

public String getInput(String s) {
    appendInput(s + ": ");
    input.requestFocus();

    synchronized (holder) {
        while (holder.isEmpty())
            //System.out.println("input inside 2"); //STOPS HERE
            try {
                holder.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        System.out.println("input inside 3");

        String nextString = holder.remove(0);

        System.out.println(nextString);
        return nextString;
    }
}

and that's where it stops, in that loop. This however also stops the whole program.

For possible solutions I have tried to isolate portions of code inside Threads, however to no avail... If you could help me revamp this code so it functions correctly or give me another way to achieve this, that would be great.


EDIT: as @markspace suggested, I tried to implement SwingWorker, firstly in the EDT thread, which did not work as the UI still got hung up, then in the getInput(String s) method. For the latter, this is what the code looked like:

public String getInput(String s) {
    waitingForInput = true;
    appendInput(s + ": ");
    input.requestFocus();
    String result = "";
    synchronized (holder) {

        worker = new SwingWorker<String, String>() {
            @Override
            public String doInBackground() {
                while (holder.isEmpty()) {
                    //System.out.println("input inside 2"); //STOPS HERE
                    try {
                        holder.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return holder.remove(0);
            }

            @Override
            public void done() {
                String s ="";
                try {
                    s = super.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                System.out.println(s);

            }
        };

        while(!worker.isDone()) {}
        try {
            result = worker.get().toString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(result);
        return result;

    }

Solution

  • I managed to solve the problem by wrapping the original Map inside another Map which calls a method when the wrapped map's put(...) is called. This passes it to my other class CommandHandler and from there it's prety self explanotary.