diff --git a/app/src/processing/app/Serial.java b/app/src/processing/app/Serial.java index 14c0933c0a6..b9432f9043f 100755 --- a/app/src/processing/app/Serial.java +++ b/app/src/processing/app/Serial.java @@ -25,9 +25,7 @@ package processing.app; //import processing.core.*; -import processing.app.debug.MessageConsumer; import static processing.app.I18n._; - import gnu.io.*; import java.io.*; @@ -55,15 +53,13 @@ public class Serial implements SerialPortEventListener { // read buffer and streams - InputStream input; + InputStreamReader input; OutputStream output; byte buffer[] = new byte[32768]; int bufferIndex; int bufferLast; - MessageConsumer consumer; - public Serial(boolean monitor) throws SerialException { this(Preferences.get("serial.port"), Preferences.getInteger("serial.debug_rate"), @@ -158,7 +154,7 @@ public Serial(String iname, int irate, if (portId.getName().equals(iname)) { //System.out.println("looking for "+iname); port = (SerialPort)portId.open("serial madness", 2000); - input = port.getInputStream(); + input = new InputStreamReader(port.getInputStream()); output = port.getOutputStream(); port.setSerialPortParams(rate, databits, stopbits, parity); port.addEventListener(this); @@ -237,62 +233,41 @@ public void dispose() { port = null; } + char serialBuffer[] = new char[4096]; - public void addListener(MessageConsumer consumer) { - this.consumer = consumer; - } - - synchronized public void serialEvent(SerialPortEvent serialEvent) { - //System.out.println("serial port event"); // " + serialEvent); - //System.out.flush(); - //System.out.println("into"); - //System.out.flush(); - //System.err.println("type " + serialEvent.getEventType()); - //System.err.println("ahoooyey"); - //System.err.println("ahoooyeysdfsdfsdf"); if (serialEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) { - //System.out.println("data available"); - //System.err.flush(); try { - while (input.available() > 0) { - //if (input.available() > 0) { - //serial = input.read(); - //serialEvent(); - //buffer[bufferCount++] = (byte) serial; + while (input.ready()) { synchronized (buffer) { if (bufferLast == buffer.length) { byte temp[] = new byte[bufferLast << 1]; System.arraycopy(buffer, 0, temp, 0, bufferLast); buffer = temp; } - //buffer[bufferLast++] = (byte) input.read(); - if(monitor == true) - System.out.print((char) input.read()); - if (this.consumer != null) - this.consumer.message("" + (char) input.read()); - - /* - System.err.println(input.available() + " " + - ((char) buffer[bufferLast-1])); - */ //} + int n = input.read(serialBuffer); + message(serialBuffer, n); } } - //System.out.println("no more"); - } catch (IOException e) { errorMessage("serialEvent", e); - //e.printStackTrace(); - //System.out.println("angry"); } catch (Exception e) { } } - //System.out.println("out of"); - //System.err.println("out of event " + serialEvent.getEventType()); } + /** + * This method is intended to be redefined by users of Serial class + * + * @param buff + * @param n + */ + protected void message(char buff[], int n) { + // Empty + } + /** * Returns the number of bytes that have been read from serial * and are waiting to be dealt with by the user. diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java index 1f34e8f7e55..7ce00474bb5 100644 --- a/app/src/processing/app/SerialMonitor.java +++ b/app/src/processing/app/SerialMonitor.java @@ -18,21 +18,21 @@ package processing.app; -import processing.app.debug.MessageConsumer; +import processing.app.debug.TextAreaFIFO; import processing.core.*; import static processing.app.I18n._; import java.awt.*; import java.awt.event.*; + import javax.swing.*; import javax.swing.border.*; -import javax.swing.event.*; import javax.swing.text.*; -public class SerialMonitor extends JFrame implements MessageConsumer { +public class SerialMonitor extends JFrame implements ActionListener { private Serial serial; private String port; - private JTextArea textArea; + private TextAreaFIFO textArea; private JScrollPane scrollPane; private JTextField textField; private JButton sendButton; @@ -40,6 +40,8 @@ public class SerialMonitor extends JFrame implements MessageConsumer { private JComboBox lineEndings; private JComboBox serialRates; private int serialRate; + private javax.swing.Timer updateTimer; + private StringBuffer updateBuffer; public SerialMonitor(String port) { super(port); @@ -67,7 +69,9 @@ public void actionPerformed(ActionEvent e) { Font editorFont = Preferences.getFont("editor.font"); Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize()); - textArea = new JTextArea(16, 40); + textArea = new TextAreaFIFO(8000000); + textArea.setRows(16); + textArea.setColumns(40); textArea.setEditable(false); textArea.setFont(font); @@ -171,6 +175,9 @@ public void actionPerformed(ActionEvent event) { } } } + + updateBuffer = new StringBuffer(1048576); + updateTimer = new javax.swing.Timer(33, this); // redraw serial monitor at 30 Hz } protected void setPlacement(int[] location) { @@ -203,9 +210,13 @@ private void send(String s) { public void openSerialPort() throws SerialException { if (serial != null) return; - - serial = new Serial(port, serialRate); - serial.addListener(this); + serial = new Serial(port, serialRate) { + @Override + protected void message(char buff[], int n) { + addToUpdateBuffer(buff, n); + } + }; + updateTimer.start(); } public void closeSerialPort() { @@ -219,13 +230,27 @@ public void closeSerialPort() { } } - public void message(final String s) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - textArea.append(s); - if (autoscrollBox.isSelected()) { - textArea.setCaretPosition(textArea.getDocument().getLength()); - } - }}); + private synchronized void addToUpdateBuffer(char buff[], int n) { + updateBuffer.append(buff, 0, n); } + + private synchronized String consumeUpdateBuffer() { + String s = updateBuffer.toString(); + updateBuffer.setLength(0); + return s; + } + + public void actionPerformed(ActionEvent e) { + final String s = consumeUpdateBuffer(); + if (s.length() > 0) { + //System.out.println("gui append " + s.length()); + if (autoscrollBox.isSelected()) { + textArea.appendTrim(s); + textArea.setCaretPosition(textArea.getDocument().getLength()); + } else { + textArea.appendNoTrim(s); + } + } + } + } diff --git a/app/src/processing/app/debug/TextAreaFIFO.java b/app/src/processing/app/debug/TextAreaFIFO.java new file mode 100644 index 00000000000..9783cd42c17 --- /dev/null +++ b/app/src/processing/app/debug/TextAreaFIFO.java @@ -0,0 +1,92 @@ +/* + Copyright (c) 2014 Paul Stoffregen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + $Id$ +*/ + +// adapted from https://community.oracle.com/thread/1479784 + +package processing.app.debug; + +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; + +public class TextAreaFIFO extends JTextArea implements DocumentListener { + private int maxChars; + private int trimMaxChars; + + private int updateCount; // limit how often we trim the document + + private boolean doTrim; + + public TextAreaFIFO(int max) { + maxChars = max; + trimMaxChars = max / 2; + updateCount = 0; + doTrim = true; + getDocument().addDocumentListener(this); + } + + public void insertUpdate(DocumentEvent e) { + if (++updateCount > 150 && doTrim) { + updateCount = 0; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + trimDocument(); + } + }); + } + } + + public void removeUpdate(DocumentEvent e) { + } + + public void changedUpdate(DocumentEvent e) { + } + + public void trimDocument() { + int len = 0; + len = getDocument().getLength(); + if (len > trimMaxChars) { + int n = len - trimMaxChars; + //System.out.println("trimDocument: remove " + n + " chars"); + try { + getDocument().remove(0, n); + } catch (BadLocationException ble) { + } + } + } + + public void appendNoTrim(String s) { + int free = maxChars - getDocument().getLength(); + if (free <= 0) + return; + if (s.length() > free) + append(s.substring(0, free)); + else + append(s); + doTrim = false; + } + + public void appendTrim(String str) { + append(str); + doTrim = true; + } +} diff --git a/build/shared/revisions.txt b/build/shared/revisions.txt index e960fd3725f..c55aec50a1c 100644 --- a/build/shared/revisions.txt +++ b/build/shared/revisions.txt @@ -13,6 +13,9 @@ ARDUINO 1.0.7 * Fixed missing NOT_AN_INTERRUPT constant in digitalPinToInterrupt() macro * Fixed performance regression in HardwareSerial::available() introduced with https://github.com/arduino/Arduino/pull/2057 +[ide] +* Mitigated Serial Monitor resource exhaustion when the connected device sends a lot of data (Paul Stoffregen) + ARDUINO 1.0.6 - 2014.09.16 [core]