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