From f6a525df752f0e90b06be7443bb6ca37e32cd17d Mon Sep 17 00:00:00 2001 From: Tiago Bagni Date: Thu, 31 Aug 2023 20:26:25 -0500 Subject: [PATCH] Clear 'My Logs' if it does not match the new set of open logs --- .../com/tibagni/logviewer/LogViewerView.kt | 57 +++++++++++- .../log/EditableLogListTableModel.kt | 32 +++++++ .../com/tibagni/logviewer/log/LogEntry.java | 19 ++++ .../logviewer/log/LogListTableModel.java | 56 +++++++----- .../tibagni/logviewer/log/LogEntryTests.kt | 88 +++++++++++++++++++ 5 files changed, 226 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/tibagni/logviewer/log/EditableLogListTableModel.kt create mode 100644 src/test/java/com/tibagni/logviewer/log/LogEntryTests.kt diff --git a/src/main/java/com/tibagni/logviewer/LogViewerView.kt b/src/main/java/com/tibagni/logviewer/LogViewerView.kt index 4a6b946..7a9e617 100644 --- a/src/main/java/com/tibagni/logviewer/LogViewerView.kt +++ b/src/main/java/com/tibagni/logviewer/LogViewerView.kt @@ -17,6 +17,8 @@ import java.awt.* import java.awt.event.* import java.io.File import javax.swing.* +import kotlin.collections.HashMap +import kotlin.collections.HashSet // This is the interface known by other views (MainView) @@ -114,7 +116,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set= logListTableModel.rowCount) { + mismatch = true + break + } + + // If the myLog no longer matches the same index in the new logs, it is a mismatch + if (!logListTableModel.getValueAt(myLogEntry.index, 0).equals(myLogEntry)) { + mismatch = true + break + } + } + + if (mismatch) { + // It is a mismatch, the logs under 'My Logs' could still match the opened logs in a different position. + // For example if the user opened the same log file again, but in addition with some other logs. + // To cover this case, make sure all logs in 'MyLogs' have a match in the opened logs. And if so, update the + // indices of the 'My Logs' + val matchedLogs = mutableListOf() + + for (i in 0 until myLogsListTableModel.rowCount) { + val myLogEntry = myLogsListTableModel.getValueAt(i, 0) as LogEntry + val matchedLogEntry = logListTableModel.getMatchingLogEntry(myLogEntry) + + if (matchedLogEntry != null) { + // Match found, update the 'My Logs' entry + matchedLogs.add(matchedLogEntry) + } + } + + // Now, we always want to update the log entries in 'MyLogs' so remove all and reinsert the ones that have + // a match + myLogsListTableModel.clear() + myLogsListTableModel.setLogs(matchedLogs) + } + } + override fun buildStreamsMenu(): JMenu? { if (logStreams.isEmpty()) return null @@ -700,6 +752,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set { private int index; @@ -84,4 +86,21 @@ public int compareTo(@NotNull LogEntry o) { // compare index if same time return time == 0 ? Integer.compare(index, o.index) : time; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LogEntry logEntry = (LogEntry) o; + return index == logEntry.index && + Objects.equals(timestamp, logEntry.timestamp) && + Objects.equals(logText, logEntry.logText) && + logLevel == logEntry.logLevel && + logStream == logEntry.logStream; + } + + @Override + public int hashCode() { + return Objects.hash(index, timestamp, logText, logLevel, logStream); + } } diff --git a/src/main/java/com/tibagni/logviewer/log/LogListTableModel.java b/src/main/java/com/tibagni/logviewer/log/LogListTableModel.java index 3061318..83a77e9 100644 --- a/src/main/java/com/tibagni/logviewer/log/LogListTableModel.java +++ b/src/main/java/com/tibagni/logviewer/log/LogListTableModel.java @@ -5,11 +5,12 @@ import javax.swing.table.AbstractTableModel; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; public class LogListTableModel extends AbstractTableModel { - private final List entries = new ArrayList<>(); - private final String title; + protected final List entries = new ArrayList<>(); + protected final String title; public LogListTableModel(String title) { this.title = title; @@ -55,29 +56,36 @@ public void setLogs(List entries) { fireTableRowsInserted(0, this.entries.size() - 1); } - public void addLogIfDoesNotExist(LogEntry entry) { - // find the nearest time pos to insert the new entry - int indexFound = Collections.binarySearch(this.entries, entry); - if (indexFound < 0) { // Element not found. Insert - int targetIndex = Math.abs(indexFound + 1); - this.entries.add(targetIndex, entry); - fireTableRowsInserted(targetIndex, targetIndex); + public @Nullable LogEntry getMatchingLogEntry(LogEntry entry) { + // Here we want to check ig the given log entry exists anywhere in the list, not necessarily in the same index, + // And we also want to make sure the text is the same. So, use a different comparator here that only considers + // the timestamp for comparison and also checks if the log text is the same + Comparator cmp = Comparator + .comparing((LogEntry o) -> o.timestamp); + + int indexFound = Collections.binarySearch(entries, entry, cmp); + if (indexFound >= 0) { + // We found one index for a possible entry. But there might be multiple log entries for the same timestamp + // so, iterate until we find the exact line we are looking for. + // First we want to find the first log in this timestamp + int i = indexFound; + while ( i >= 0 && entries.get(i).timestamp.equals(entry.timestamp)) { + i--; + } + + // Now that we are in the beginning of the timestamp, look for the entry + while (!entries.get(i).logText.equals(entry.logText) && + entries.get(i).timestamp.compareTo(entry.timestamp) <= 0) { + i++; + } + + // We either found or finished search. check which one + if (entries.get(i).logText.equals(entry.logText)) { + return entries.get(i); + } } - } - - public void removeLog(LogEntry entry) { - int index = this.entries.indexOf(entry); - if (index != -1) { - this.entries.remove(index); - fireTableRowsDeleted(index, index); - } - } - - public void clearLog() { - if (this.entries.isEmpty()) return; - int index = this.entries.size() - 1; - this.entries.clear(); - fireTableRowsDeleted(0, index); + // Not found + return null; } } diff --git a/src/test/java/com/tibagni/logviewer/log/LogEntryTests.kt b/src/test/java/com/tibagni/logviewer/log/LogEntryTests.kt new file mode 100644 index 0000000..d7fa4aa --- /dev/null +++ b/src/test/java/com/tibagni/logviewer/log/LogEntryTests.kt @@ -0,0 +1,88 @@ +package com.tibagni.logviewer.log + +import org.junit.Assert.* +import org.junit.Test + +class LogEntryTests { + + @Test + fun testLogEntryEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + + assertEquals(entry1, entry2) + assertEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTextNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text2", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryLevelNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.INFO, LogTimestamp(9, 1, 8, 0, 0, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTimestampMonthNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(8, 1, 8, 0, 0, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTimestampDayNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 0, 0, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTimestampHourNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 3, 0, 0, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTimestampMinutesNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 10, 0, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTimestampSecondsNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 0, 30, 0)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } + + @Test + fun testLogEntryTimestampHundredthNotEquals() { + val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0)) + val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 0, 0, 90)) + + assertNotEquals(entry1, entry2) + assertNotEquals(entry1.hashCode(), entry2.hashCode()) + } +} \ No newline at end of file