Issue #19

Running application which reads from console hangs the Modelio

Added by Marijan Matic over 8 years ago. Updated about 8 years ago.

Status:ClosedStart date:16 November 2011
Priority:LowDue date:
Assignee:Christophe Malgouyres% Done:

100%

Category:-
Target version:2.0.13

Description

If the application is started from Modelio interacts with a user through console, Modelio will hang. Only killing the “java.exe” process will make Modelio to resume.

For example:

public class AppMain {
    public static void main(final String[] args) throws IOException {       
        System.out.print("Enter number: ");
        int num = System.in.read();     
    }
}

The problem is in module

com.modeliosoft.modelio.javadesigner.utils.ProcessManager 

It redirects stdout and stderr processing in separate threads, but stdin is not processed in separated thread which causes the plugin to hang the whole Modelio. It is necessarry to fork the stdin handling in separate process, something like:

    new Thread () {
            @Override
            public void run() {
                try {
                    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));

                    String line = "";
                    try {
                        while ( "other threads are running" ) {
                            // Traitement du flux d'input
                            // SLEEP
                        }
                    } finally {
                        writer.close();
                    }
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }.start();

Of course this will make Modelio not to hang, but the application will not work properly, because the text area of

com.modeliosoft.modelio.javadesigner.dialog.InfoDialog

is readonly (SWT.READ_ONLY). It is possible to set it as RW, and handle the keystrokes which will be transfred to

com.modeliosoft.modelio.javadesigner.dialog.JConsoleWithDialog

but maybe it is easier to include a possibility to start the application in the external shell.

For example, defining new app properties, and using the call of (for example)

boolean inExternalShell = mdac.getConfiguration ().getBooleanParameterValue(JavaDesignerParameters.RUNINEXTERNALSHELL)

inside the

com.modeliosoft.modelio.javadesigner.commands.RunApplication

and (example for windows) adding “cmd /c” to command

String command = "java -classpath \"" + classpath + "\" " + mainClass + " " + executionArguments.getArguments();

if (System.getProperty ("os.name").startsWith ("Windows")) { 
         command += " & pause";            
         if( inExternalShell ) {
             command = "cmd /c start " + command;
         } 
}

of course for linux/unix some kind of xterm invocation should be used instead of “cmd /c”.

I tested without adding special property. I don’t know where to put this property, inside General settings, or in new category like Running.

jd.com.zip (7.12 KB) Marijan Matic, 06 February 2012 14:08

History

#1 Updated by Christophe Demeulemeester over 8 years ago

  • Target version deleted (2.0.11)

JavaDesigner 2.0.11 is closed, this issue will be fixed in a later version.

#2 Updated by Christophe Malgouyres over 8 years ago

Flushing the stdin stream seems needed, but i’m not sure invoking an external shell is the right choice to make. It would introduce a slightly different behavior between windows and linux, which i was trying to avoid…

Defining a new module property isn’t really needed, it could be an option in the “run” dialog box, next to the program’s arguments. I try not to add multiple module properties, as they can’t really be synchronized between projects, and add complexity in the module’s configuration.

#3 Updated by Marijan Matic over 8 years ago

If the external shell is not an option, than you can took in consideration the solution where InfoDialog is RW, and captures the keystorkes which can be read by JConsoleWithDialog (with, for example readInfo message).

You need to change 3 files – InfoDialog, JConsoleWithDialog, and ProcessManager.

Eclipse and NetBeans have their own console, which have possibilities to read input (using Scanner or System.in, but NOT via System.console() ).

What do you think?

#4 Updated by Christophe Malgouyres over 8 years ago

I agree, setting InfoDialog to read-write seems an appropriate answer for this problem.

#5 Updated by Marijan Matic about 8 years ago

Finally I found some time to spend again regarding this topic. I think that the run application framework should be refactored. I noticed few other issues.

Class com.modeliosoft.modelio.javadesigner.commands.RunAppliaction invokes the ProcessManager with parameter wait = true. This means that the actionPerformed is blocked until the process is not finished. As long the actionPerformed is blocked, the Modelio AWT event thread can’t continue with processing. This is why Modelio hangs when user aplication waits on read. Introducting new thread in ProcessManager for input reading does not help, because ProcessManager waits for all threads to finish. In my case this worked, because I used external shell, but as soon I started working on RW InfoDialog, I run on this problem. Changing the call in RunApplication from

manager.execute(command, true);

to:

manager.execute(command, false);

helps, but than the problem is that when user press “Close” button the process is not terminated. It runs in the background, and it is not possible terminate it any more. The process console is not visible any more.

One more thing, RunApplication in actionPerformed method appends “& pause” if the OS is windows. When you compile and start the following code on Windows OS without setting any command line parameters:

public static void main(final String[] args) throws IOException {
    System.out.printf("%s - args.length=%d\n", java.util.Arrays.toString(args), args.length);
}

You get the following output

[&, pause] - args.length=2

This should not work that way, and should be removed. This is all for today.

#6 Updated by Marijan Matic about 8 years ago

In the attachment are the changes to use InfoDialog for console input too.

CHANGES

*** com.modeliosoft.modelio.javadesigner.commands.RunApplication

- Extra string to command line - "&pause" is removed.
- stores the process reference to console object, so it can be terminated on dialog close
- don't wait for process to end inside actionListener

*** com.modeliosoft.modelio.javadesigner.dialog.InfoDialog

- set Text widget to RW
- change the method name getTextString returns the String content of Text widget 
  Maybe it can be removed, so the text content can be read using getText().getText()
- method getText returns the reference to widget Text

*** com.modeliosoft.modelio.javadesigner.dialog.JConsoleWithDialog

- adds deque for keystrokes and queue for input lines
- implements keyListener on dialog.text for keystroke capture
  Here should be observer the CR/LF difference with SWT Text widget and CMD shell or Swing JTextArea.
- implements disposeListener on dialog.text to terminate process on dispose (if no, it remains active )
- implements method readInt which returns single character from the queue

*** com.modeliosoft.modelio.javadesigner.util.ProcessManager

- process variable is an attribute 
- added terminate method to terminate the process on dialog close
  current implementation use destroy() method from Process class, but maybe better approach would be to    use Thread.interrupt() method on all stream threads.
- changed handling of process STDIN stream
  • NOTE: The changes are performed on source version 2.0.11.*

#7 Updated by Christophe Malgouyres about 8 years ago

Thank you for these changes, i’ll check them as soon as possible.

#8 Updated by Marijan Matic about 8 years ago

In the provided implementation, it is possible to change the caret position inside the Text widget. This may be anoying if the user click the mouse during the input. Some mouse listener should be added to to provide only the selection possibility, but not the change od the caret position.

#9 Updated by Christophe Malgouyres about 8 years ago

  • Status changed from New to Resolved
  • Assignee set to Christophe Malgouyres
  • Target version set to 2.0.13

Fixes by revision 110 thanks to your changes proposal.

The main change i added is a dispose listener in ProcessManager destroying the process and closing the output reading thread when the console is closed.

A few checks were also missing for the console to run with a ‘null’ InfoDialog. This use case is related to peer services calls with a disabled GUI, for example in Modelio’s automatic integration scripts.

#10 Updated by Marijan Matic about 8 years ago

This is a good news. The only missing part is to handle caret position (you can change the caret position during input). One possible workaround is to attach mouse listener on text component, and on each button release to add an empty string to the text data. Something like:

 private MouseAdapter mouseAdapter = new MouseAdapter() {                    
        public void mouseUp(MouseEvent me) {                
            super.mouseUp(me);                
            dialog.addText("");
        }

    };

But this will not prevent the arrow keys to change the caret position. In a NetBeans, you can change the caret position with a mouse and with the arrow keys, but once you start typing text, caret is returned back to original position. This may be the improvement for some future release.

#11 Updated by Marijan Matic about 8 years ago

This will do the trick with the caret. If it’s not too late for version 2.0.13.

Just replace the code inside the JConsoleDialog constructor, KeyAdapter implementation, first part of the keyPress method, where keys are stored in deque …

  /* In case of backspace, remove character from */            
  if (ke.character == SWT.BS) {
     if (typingDeque.size() > 0) {
        typingDeque.removeLast();
     }
  } else {
     typingDeque.offer(ke.character);
  }

just replace with this implementation

  /* In case of backspace, remove character from */            
  if (ke.character == SWT.BS) {
     if (typingDeque.size() > 0) {
        typingDeque.removeLast();
      }
  } else { 
     // in case of non control keys, and if the caret was moved ...
     if( (ke.character > 0) && ( dialog.getText().getCaretPosition() < dialog.getTextString().length() ) ){
           // ... append empty string, which will posiiton the caret at the end
           dialog.getText().append("");
     }
     typingDeque.offer(ke.character);
  }  

This works fine. Even the caret is changed next char typing will start at the end of the text (where the input is expected). Now it works fine.

#12 Updated by Marijan Matic about 8 years ago

I did not took in consideration all possibilities with BACKSPACE, DELETE, ARROW and CR/LF keys. This implementation works pretty good.

typingDeque was modified to typingList:

private ArrayList<Character> typingList = new ArrayList<Character>();

Now is possible to insert the keys relatively to caret position. Here is the whole KeyListener which works pretty OK with BACKSPACE, DELETE, CR/LF, ARROW KEYS and other mouse movement. package com.modeliosoft.modelio.javadesigner.dialog, class JConsoleWithDialog, inside constructor.

dialog.getText().addKeyListener( new KeyAdapter() {

        public void keyPressed(final KeyEvent ke) {

            super.keyPressed(ke);

                /*
                 * It's not possible to modify the text characters outside
                 * the writable area, which begins at:
                 * 
                 * dialog.getTextString().length() - typingList.size()
                 *
                 */
                int charPos = dialog.getText().getCaretPosition()
                              + typingList.size()
                              - dialog.getTextString().length();


                if (ke.character > 0){
                    if( charPos < 0) {
                       /*
                        * If the caret is outside writable area, go to last character in text. 
                        * append statement can be replaced also with setSelection:
                        *    dialog.getText().setSelection( dialog.getTextString().length() );
                        */
                       dialog.getText().append("");
                       // update character position 
                       charPos = typingList.size();
                    }
                }

                // in case of BACKSPACE, remove previous character in the list
                if (ke.character == SWT.BS) {
                    if (typingList.size() > 0 && charPos > 0 ) {
                        typingList.remove(charPos-1);
                    } else {
                      // suppress event: don't delete character from text component
                      // if it's not deleted from the list too
                      ke.doit = false;
                    }
                // in case of DELETE, remove next character in the list    
                } else if (ke.character == SWT.DEL) {
                    if (typingList.size() > 0 && charPos >= 0 ) {
                        typingList.remove(charPos);
                    } else {
                      // suppress event: don't delete character from text component
                      // if it's not deleted from the list too
                      ke.doit = false;
                    }
                } else if (ke.character == SWT.CR || ke.character == SWT.LF) {                        
                   /* Windows CMD.EXE and Swing JTextArea returns LF (\n) when ENTER key is pressed,
                    * SWT Text widget returns CR (\r), and this causes troubles when reading input
                    * with java.util.Scanner. CR should be replaced with LF to work correctly.
                    */
                    typingList.add(SWT.LF );                        

                    lineQueue.addAll(typingList);
                    typingList.clear();

                    /* all new line characters, even if they are in the middle 
                     * of the writable area will be treated as the new line for 
                     * the whole input.
                     * use call to append, to show properly the line break at the 
                     * end of the input
                     */                         
                    dialog.getText().append("");                        

                } else if( ke.character > 0 ) {
                    // don't read control keys, and insert characters
                    // inside the list relatively to the caret position
                    // NOTE: checking the value of charPos is not necessary
                    typingList.add(charPos, ke.character);
                }

        }

    } );

#13 Updated by Christophe Malgouyres about 8 years ago

Improvement added to JConsoleWithDialog in revision 121.

As far as i’m concerned, returning the caret to the appropriate zone when trying to edit some text outside of the edition zone is an appropriate solution.

Thank you for your contributions on this topic!

#14 Updated by Christophe Demeulemeester about 8 years ago

  • Status changed from Resolved to Closed

Also available in: Atom PDF