Само терминал Java Отпечатване на изход от първата команда

РЕДАКТИРАНЕ: Кодът вече работи! Ето как го направих:

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    private File workingFolder = FileSystemView.getFileSystemView().getDefaultDirectory();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        Process bash = new ProcessBuilder("bash").directory(workingFolder).start();

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        int code = bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                        if (code == 0 && command.split(" ").length > 1) workingFolder = new File(command.split(" ")[1]);

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            if (lines.length == 0) return;
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}

Работя върху терминално приложение на Java. Работи с изключение на това, че отпечатва само изхода на първата команда. Ето снимка на GUI, когато се опитвам да стартирам ls повече от веднъж.

JTerminal GUI

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final Process bash = new ProcessBuilder("/bin/bash").start();

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}

person nrubin29    schedule 27.08.2013    source източник
comment
Исках да тествам това, но очевидно не работи на Mac. Опитахте ли да го дебъгвате, за да видите къде се губят данните?   -  person GGrec    schedule 28.08.2013
comment
О, добре, тогава може да ми липсват JAR файлове. Продължете и го отстранете.   -  person GGrec    schedule 28.08.2013
comment
@GGrec Трябва да работи на Mac, тествах моето решение с помощта на един...   -  person MadProgrammer    schedule 28.08.2013


Отговори (2)


Обект Process може да се използва само веднъж, така че последващите извиквания към Process.waitFor() се връщат веднага (тъй като процесът вече е прекратен). Вместо това трябва да поискате нов процес всеки път от вашия ProcessBuilder.

Ето правилния код:

package me.nrubin29.jterminal;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final ProcessBuilder builder = new ProcessBuilder("/bin/bash");

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                                Process bash = builder.start();
                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}
person Menthos    schedule 27.08.2013
comment
Или можете да зададете работната директория на ProcessBuilder след всяка команда: docs.oracle.com/javase/6/docs/api/java/lang/ - person Menthos; 28.08.2013
comment
На прав път, но бих се притеснявал за bash.waitFor, след като се върне, това ще означава, че процесът е излязъл. Също така, waitingFor трябва да се направи СЛЕД прочитане на входа на процеса... - person MadProgrammer; 28.08.2013
comment
@Menthos Или можете да зададете работната директория на ProcessBuilder след всяка команда: - Все още ще изисква да създавате нов Process за всяка нова команда, проваляйки целта... - person MadProgrammer; 28.08.2013
comment
@MadProgrammer Също така, waitingFor трябва да се направи СЛЕД четене на входа на процеса... Да, това е вярно и особено защото не можете да разчитате на буферите на изходния поток, които са зависими от системата. От друга страна правилното боравене с това е извън обхвата на въпросите :) - person Menthos; 28.08.2013
comment
@Menthos Възможно е, но коментирането във вашия отговор може да облекчи OP да се върне при вас и да попита защо нещо друго не работи;) - person MadProgrammer; 28.08.2013

След като даден процес е излязъл, той не може да бъде "четен" или "записан" в него.

Вашият код също ще блокира метода "waitFor", което означава, че потребителският ви интерфейс няма да бъде актуализиран, докато процесът не приключи. Това наистина е полезно...

Вместо това трябва да стартирате процеса и да му позволите да продължи да работи, наблюдавайки състоянието във фонов режим.

Тъй като Swing е среда с една нишка, трябва да внимавате как се справяте с дълго изпълнявани/блокиращи процеси и актуализации на потребителския интерфейс.

За тази цел използвах SwingWorker, за да прочета изхода на Process във фонова нишка и повторно синхронизиране на актуализациите обратно към нишката за изпращане на събития. Това позволява на потребителския интерфейс да продължи да работи и да остане отзивчив, докато изходът от работещия процес се чете в...

Също така, вместо да "пускате" нова команда всеки път, създавайки нов процес, ще трябва да пишете във входния поток на Process.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestTerminal {

    public static void main(String[] args) {
        new TestTerminal();
    }

    public TestTerminal() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JTextArea output;
        private JTextField input;

        private Process process;

        public TestPane() {
            setLayout(new BorderLayout());

            output = new JTextArea(20, 20);
            input = new JTextField(10);

            output.setLineWrap(false);
            output.setWrapStyleWord(false);
            output.setEditable(false);
            output.setFocusable(false);

            add(new JScrollPane(output));
            add(input, BorderLayout.SOUTH);

            input.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String cmd = input.getText() + "\n";
                    input.setText(null);
                    output.append("\n" + cmd + "\n\n");
                    if (process == null) {
                        ProcessBuilder pb = new ProcessBuilder("bash");
                        pb.directory(new File("."));
                        try {
                            process = pb.start();
                            InputStreamWorker isw = new InputStreamWorker(output, process.getInputStream());
                            isw.execute();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                            input.setEnabled(false);
                        }

                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                int exit = -1;
                                try {
                                    exit = process.waitFor();
                                } catch (InterruptedException ex) {
                                }
                                System.out.println("Exited with " + exit);
                                input.setEnabled(false);
                            }
                        }).start();

                    }
                    OutputStream os = process.getOutputStream();
                    try {
                        os.write(cmd.getBytes());
                        os.flush();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                        input.setEnabled(false);
                    }
                }
            });
        }

    }

    public class InputStreamWorker extends SwingWorker<Void, Character> {

        private InputStream is;
        private JTextArea output;

        public InputStreamWorker(JTextArea output, InputStream is) {
            this.is = is;
            this.output = output;
        }

        @Override
        protected void process(List<Character> chunks) {
            StringBuilder sb = new StringBuilder(chunks.size());
            for (Character c : chunks) {
                sb.append(c);
            }
            output.append(sb.toString());
        }

        @Override
        protected Void doInBackground() throws Exception {
            int in = -1;
            while ((in = is.read()) != -1) {
                publish((char)in);
            }
            return null;
        }

    }

}

Също така бих ви препоръчал да избягвате KeyListener, където можете. JTextField използва ActionListener, който ще бъде извикан, когато потребителят натисне клавиша "действие", каквото и да е това за дадената платформа...

person MadProgrammer    schedule 27.08.2013