diff --git a/.classpath b/.classpath
index eabc35c4180..82109ab2f11 100644
--- a/.classpath
+++ b/.classpath
@@ -11,6 +11,9 @@
+
+
+
diff --git a/app/.classpath b/app/.classpath
index 51172fa7c40..4709754cdb5 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -53,4 +53,7 @@
+
+
+
diff --git a/app/src/processing/app/AbstractTextMonitor.java b/app/src/processing/app/AbstractTextMonitor.java
index fdfcfba760a..5121dc76ac2 100644
--- a/app/src/processing/app/AbstractTextMonitor.java
+++ b/app/src/processing/app/AbstractTextMonitor.java
@@ -13,6 +13,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.Box;
import javax.swing.BoxLayout;
@@ -43,13 +44,10 @@ public abstract class AbstractTextMonitor extends AbstractMonitor {
protected JComboBox lineEndings;
protected JComboBox serialRates;
- private SimpleDateFormat logDateFormat;
-
public AbstractTextMonitor(BoardPort boardPort) {
super(boardPort);
- logDateFormat = new SimpleDateFormat("HH:mm:ss.SSS -> ");
}
-
+
protected void onCreateWindow(Container mainPane) {
Font consoleFont = Theme.getFont("console.font");
Font editorFont = PreferencesData.getFont("editor.font");
@@ -57,7 +55,7 @@ protected void onCreateWindow(Container mainPane) {
mainPane.setLayout(new BorderLayout());
- textArea = new TextAreaFIFO(8000000);
+ textArea = new TextAreaFIFO(8_000_000);
textArea.setRows(16);
textArea.setColumns(40);
textArea.setEditable(false);
@@ -70,7 +68,7 @@ protected void onCreateWindow(Container mainPane) {
scrollPane = new JScrollPane(textArea);
mainPane.add(scrollPane, BorderLayout.CENTER);
-
+
JPanel upperPane = new JPanel();
upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.X_AXIS));
upperPane.setBorder(new EmptyBorder(4, 4, 4, 4));
@@ -165,7 +163,7 @@ public void onSendCommand(ActionListener listener) {
textField.addActionListener(listener);
sendButton.addActionListener(listener);
}
-
+
public void onClearCommand(ActionListener listener) {
clearButton.addActionListener(listener);
}
@@ -173,41 +171,57 @@ public void onClearCommand(ActionListener listener) {
public void onSerialRateChange(ActionListener listener) {
serialRates.addActionListener(listener);
}
-
- public void message(final String s) {
- SwingUtilities.invokeLater(new Runnable() {
- // Pre-allocate all objects used for streaming data
- Date t = new Date();
- String now;
- StringBuilder out = new StringBuilder(16384);
- boolean isStartingLine = false;
-
- public void run() {
- if (addTimeStampBox.isSelected()) {
- t.setTime(System.currentTimeMillis());
- now = logDateFormat.format(t);
- out.setLength(0);
-
- StringTokenizer tokenizer = new StringTokenizer(s, "\n", true);
- while (tokenizer.hasMoreTokens()) {
- if (isStartingLine) {
- out.append(now);
- }
- String token = tokenizer.nextToken();
- out.append(token);
- // tokenizer returns "\n" as a single token
- isStartingLine = token.charAt(0) == '\n';
- }
-
- textArea.append(out.toString());
- } else {
- textArea.append(s);
- }
- if (autoscrollBox.isSelected()) {
- textArea.setCaretPosition(textArea.getDocument().getLength());
+ public void message(final String msg) {
+ SwingUtilities.invokeLater(new UpdateTextAreaAction(textArea,
+ addTimeStampBox.isSelected(),
+ autoscrollBox.isSelected(),
+ msg));
+ }
+
+ static class UpdateTextAreaAction implements Runnable {
+
+ private static final String LINE_SEPARATOR = "\n";
+ private static AtomicBoolean isStartingLine = new AtomicBoolean(true);
+
+ private String msg;
+ private boolean addTimeStamp;
+ private boolean doAutoscroll;
+ private TextAreaFIFO textArea;
+
+ UpdateTextAreaAction(TextAreaFIFO textArea, boolean addTimeStamp,
+ boolean doAutoscroll, String msg) {
+ this.msg = msg;
+ this.textArea = textArea;
+ this.addTimeStamp = addTimeStamp;
+ this.doAutoscroll = doAutoscroll;
+ }
+
+ public void run() {
+ if (addTimeStamp) {
+ textArea.append(addTimestamps(msg));
+ } else {
+ textArea.append(msg);
+ }
+ if (doAutoscroll) {
+ textArea.setCaretPosition(textArea.getDocument().getLength());
+ }
+ }
+
+ private String addTimestamps(String text) {
+ String now = new SimpleDateFormat("HH:mm:ss.SSS -> ").format(new Date());
+ final StringBuilder sb = new StringBuilder(text.length() + now.length());
+ StringTokenizer tokenizer = new StringTokenizer(text, LINE_SEPARATOR, true);
+ while (tokenizer.hasMoreTokens()) {
+ if (isStartingLine.get()) {
+ sb.append(now);
}
+ String token = tokenizer.nextToken();
+ sb.append(token);
+ // tokenizer returns "\n" as a single token
+ isStartingLine.set(token.equals(LINE_SEPARATOR));
}
- });
+ return sb.toString();
+ }
}
}
diff --git a/app/test-lib/byte-buddy-1.9.1.jar b/app/test-lib/byte-buddy-1.9.1.jar
new file mode 100644
index 00000000000..e1fd7639458
Binary files /dev/null and b/app/test-lib/byte-buddy-1.9.1.jar differ
diff --git a/app/test-lib/mockito-core-2.23.0.jar b/app/test-lib/mockito-core-2.23.0.jar
new file mode 100644
index 00000000000..be1b8c604e1
Binary files /dev/null and b/app/test-lib/mockito-core-2.23.0.jar differ
diff --git a/app/test-lib/objenesis-3.0.jar b/app/test-lib/objenesis-3.0.jar
new file mode 100644
index 00000000000..d22b9bd57c4
Binary files /dev/null and b/app/test-lib/objenesis-3.0.jar differ
diff --git a/app/test/processing/app/UpdateTextAreaActionTest.java b/app/test/processing/app/UpdateTextAreaActionTest.java
new file mode 100644
index 00000000000..a5e751bac9e
--- /dev/null
+++ b/app/test/processing/app/UpdateTextAreaActionTest.java
@@ -0,0 +1,100 @@
+package processing.app;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class UpdateTextAreaActionTest {
+
+ private static final String TIMESTAMP_REGEX = "\\d\\d:\\d\\d:\\d\\d.\\d\\d\\d";
+ private TextAreaFIFO textAreaFIFO;
+ private ArgumentCaptor text;
+
+ @Before public void setUp() {
+ textAreaFIFO = mock(TextAreaFIFO.class);
+ text = ArgumentCaptor.forClass(String.class);
+ sendNewLineInOrderToHaveCleanTestStart();
+ }
+
+ @Test
+ public void noTimestampAdded() {
+ // given
+ AbstractTextMonitor.UpdateTextAreaAction action = new AbstractTextMonitor.UpdateTextAreaAction(
+ textAreaFIFO, false, false, "line1\nline2\r\nline3");
+
+ // when
+ action.run();
+
+ //then
+ verify(textAreaFIFO, atLeastOnce()).append(text.capture());
+ assertThat(text.getValue()).startsWith("line1");
+ }
+
+ @Test
+ public void all3LinesHaveTimestampAdded() {
+ // given
+ AbstractTextMonitor.UpdateTextAreaAction action = new AbstractTextMonitor.UpdateTextAreaAction(
+ textAreaFIFO, true, false, "line1\nline2\r\nline3");
+
+ // when
+ action.run();
+
+ //then
+ verify(textAreaFIFO, atLeastOnce()).append(text.capture());
+ assertThat(text.getValue()).matches(TIMESTAMP_REGEX + " -> line1\\n"
+ + TIMESTAMP_REGEX + " -> line2\\r\\n"
+ + TIMESTAMP_REGEX + " -> line3");
+ }
+
+ @Test
+ public void emptyLinesHaveTimestampToo() {
+ // given
+ AbstractTextMonitor.UpdateTextAreaAction action = new AbstractTextMonitor.UpdateTextAreaAction(
+ textAreaFIFO, true, false, "line_1\n\nline_2");
+
+ // when
+ action.run();
+
+ //then
+ verify(textAreaFIFO, atLeastOnce()).append(text.capture());
+ assertThat(text.getValue()).matches(TIMESTAMP_REGEX + " -> line_1\\n"
+ + TIMESTAMP_REGEX + " -> \\n"
+ + TIMESTAMP_REGEX + " -> line_2");
+ }
+
+ @Test
+ public void newLinesAreRememberedWhenNewBufferIsUsed() {
+ // given #1
+ AbstractTextMonitor.UpdateTextAreaAction action;
+ action = new AbstractTextMonitor.UpdateTextAreaAction(
+ textAreaFIFO, true, false, "first line without newline");
+
+ // when #1
+ action.run();
+
+ //then #1
+ verify(textAreaFIFO, atLeastOnce()).append(text.capture());
+ assertThat(text.getValue()).matches(TIMESTAMP_REGEX + " -> first line without newline");
+
+ // given #2
+ action = new AbstractTextMonitor.UpdateTextAreaAction(
+ textAreaFIFO, true, false, "more text for first line");
+
+ // when #2
+ action.run();
+
+ //then #2
+ verify(textAreaFIFO, atLeastOnce()).append(text.capture());
+ assertThat(text.getValue()).matches("more text for first line");
+ }
+
+
+ private void sendNewLineInOrderToHaveCleanTestStart() {
+ AbstractTextMonitor.UpdateTextAreaAction action = new AbstractTextMonitor.UpdateTextAreaAction(
+ textAreaFIFO, true, false, "\n");
+ action.run();
+ }
+}
\ No newline at end of file