Skip to content

Commit f6a525d

Browse files
committed
Clear 'My Logs' if it does not match the new set of open logs
1 parent ad97de7 commit f6a525d

File tree

5 files changed

+226
-26
lines changed

5 files changed

+226
-26
lines changed

src/main/java/com/tibagni/logviewer/LogViewerView.kt

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import java.awt.*
1717
import java.awt.event.*
1818
import java.io.File
1919
import javax.swing.*
20+
import kotlin.collections.HashMap
21+
import kotlin.collections.HashSet
2022

2123

2224
// This is the interface known by other views (MainView)
@@ -114,7 +116,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
114116

115117
private lateinit var logListTableModel: LogListTableModel
116118
private lateinit var filteredLogListTableModel: LogListTableModel
117-
private lateinit var myLogsListTableModel: LogListTableModel
119+
private lateinit var myLogsListTableModel: EditableLogListTableModel
118120
private val logRenderer: LogCellRenderer
119121
private val myLogsRenderer: LogCellRenderer
120122

@@ -555,6 +557,56 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
555557
})
556558
}
557559

560+
// TODO Move 'My Logs' handling logic to the presenter/repository
561+
private fun clearMyLogsIfNeeded() {
562+
// We only want to clear logs in 'My Logs' if the new logs that are being loaded are different
563+
// So we check if the logs under 'My Logs' are still matching the logs that are open. If not, clear
564+
// This is to avoid clearing 'My Logs' when the user is simply refreshing the logs (F5)
565+
if (myLogsListTableModel.rowCount == 0) {
566+
return
567+
}
568+
569+
var mismatch = false
570+
for (i in 0 until myLogsListTableModel.rowCount) {
571+
val myLogEntry = myLogsListTableModel.getValueAt(i, 0) as LogEntry
572+
573+
// If myLog's index is greater than the number of new logs, it is a mismatch
574+
if (myLogEntry.index >= logListTableModel.rowCount) {
575+
mismatch = true
576+
break
577+
}
578+
579+
// If the myLog no longer matches the same index in the new logs, it is a mismatch
580+
if (!logListTableModel.getValueAt(myLogEntry.index, 0).equals(myLogEntry)) {
581+
mismatch = true
582+
break
583+
}
584+
}
585+
586+
if (mismatch) {
587+
// It is a mismatch, the logs under 'My Logs' could still match the opened logs in a different position.
588+
// For example if the user opened the same log file again, but in addition with some other logs.
589+
// To cover this case, make sure all logs in 'MyLogs' have a match in the opened logs. And if so, update the
590+
// indices of the 'My Logs'
591+
val matchedLogs = mutableListOf<LogEntry>()
592+
593+
for (i in 0 until myLogsListTableModel.rowCount) {
594+
val myLogEntry = myLogsListTableModel.getValueAt(i, 0) as LogEntry
595+
val matchedLogEntry = logListTableModel.getMatchingLogEntry(myLogEntry)
596+
597+
if (matchedLogEntry != null) {
598+
// Match found, update the 'My Logs' entry
599+
matchedLogs.add(matchedLogEntry)
600+
}
601+
}
602+
603+
// Now, we always want to update the log entries in 'MyLogs' so remove all and reinsert the ones that have
604+
// a match
605+
myLogsListTableModel.clear()
606+
myLogsListTableModel.setLogs(matchedLogs)
607+
}
608+
}
609+
558610
override fun buildStreamsMenu(): JMenu? {
559611
if (logStreams.isEmpty()) return null
560612

@@ -700,6 +752,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
700752
logListTableModel.setLogs(logEntries)
701753
// calc the line number view needed width
702754
logEntries?.lastOrNull()?.let { logRenderer.recalculateLineNumberPreferredSize(it.index) }
755+
clearMyLogsIfNeeded()
703756
}
704757

705758
override fun showCurrentLogsLocation(logsPath: String?) {
@@ -893,7 +946,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
893946
filteredLogList = SearchableTable(filteredLogListTableModel)
894947
logsPane.rightComponent = filteredLogList // Right or below (below in this case)
895948

896-
myLogsListTableModel = LogListTableModel("My Logs")
949+
myLogsListTableModel = EditableLogListTableModel("My Logs")
897950
myLogsList = SearchableTable(myLogsListTableModel)
898951

899952
mainLogSplit.leftComponent = logsPane
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.tibagni.logviewer.log
2+
3+
import java.util.*
4+
import kotlin.math.abs
5+
6+
class EditableLogListTableModel(title: String) : LogListTableModel(title) {
7+
8+
fun addLogIfDoesNotExist(entry: LogEntry) {
9+
// find the nearest time pos to insert the new entry
10+
val indexFound = Collections.binarySearch(entries, entry)
11+
if (indexFound < 0) { // Element not found. Insert
12+
val targetIndex = abs(indexFound + 1)
13+
entries.add(targetIndex, entry)
14+
fireTableRowsInserted(targetIndex, targetIndex)
15+
}
16+
}
17+
18+
fun removeLog(entry: LogEntry) {
19+
val index = entries.indexOf(entry)
20+
if (index != -1) {
21+
entries.removeAt(index)
22+
fireTableRowsDeleted(index, index)
23+
}
24+
}
25+
26+
fun clear() {
27+
if (entries.isEmpty()) return
28+
val index = entries.size - 1
29+
entries.clear()
30+
fireTableRowsDeleted(0, index)
31+
}
32+
}

src/main/java/com/tibagni/logviewer/log/LogEntry.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.jetbrains.annotations.NotNull;
55
import org.jetbrains.annotations.Nullable;
66

7+
import java.util.Objects;
8+
79
public class LogEntry implements Comparable<LogEntry> {
810

911
private int index;
@@ -84,4 +86,21 @@ public int compareTo(@NotNull LogEntry o) {
8486
// compare index if same time
8587
return time == 0 ? Integer.compare(index, o.index) : time;
8688
}
89+
90+
@Override
91+
public boolean equals(Object o) {
92+
if (this == o) return true;
93+
if (o == null || getClass() != o.getClass()) return false;
94+
LogEntry logEntry = (LogEntry) o;
95+
return index == logEntry.index &&
96+
Objects.equals(timestamp, logEntry.timestamp) &&
97+
Objects.equals(logText, logEntry.logText) &&
98+
logLevel == logEntry.logLevel &&
99+
logStream == logEntry.logStream;
100+
}
101+
102+
@Override
103+
public int hashCode() {
104+
return Objects.hash(index, timestamp, logText, logLevel, logStream);
105+
}
87106
}

src/main/java/com/tibagni/logviewer/log/LogListTableModel.java

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import javax.swing.table.AbstractTableModel;
66
import java.util.ArrayList;
77
import java.util.Collections;
8+
import java.util.Comparator;
89
import java.util.List;
910

1011
public class LogListTableModel extends AbstractTableModel {
11-
private final List<LogEntry> entries = new ArrayList<>();
12-
private final String title;
12+
protected final List<LogEntry> entries = new ArrayList<>();
13+
protected final String title;
1314

1415
public LogListTableModel(String title) {
1516
this.title = title;
@@ -55,29 +56,36 @@ public void setLogs(List<LogEntry> entries) {
5556
fireTableRowsInserted(0, this.entries.size() - 1);
5657
}
5758

58-
public void addLogIfDoesNotExist(LogEntry entry) {
59-
// find the nearest time pos to insert the new entry
60-
int indexFound = Collections.binarySearch(this.entries, entry);
61-
if (indexFound < 0) { // Element not found. Insert
62-
int targetIndex = Math.abs(indexFound + 1);
63-
this.entries.add(targetIndex, entry);
64-
fireTableRowsInserted(targetIndex, targetIndex);
59+
public @Nullable LogEntry getMatchingLogEntry(LogEntry entry) {
60+
// Here we want to check ig the given log entry exists anywhere in the list, not necessarily in the same index,
61+
// And we also want to make sure the text is the same. So, use a different comparator here that only considers
62+
// the timestamp for comparison and also checks if the log text is the same
63+
Comparator<LogEntry> cmp = Comparator
64+
.comparing((LogEntry o) -> o.timestamp);
65+
66+
int indexFound = Collections.binarySearch(entries, entry, cmp);
67+
if (indexFound >= 0) {
68+
// We found one index for a possible entry. But there might be multiple log entries for the same timestamp
69+
// so, iterate until we find the exact line we are looking for.
70+
// First we want to find the first log in this timestamp
71+
int i = indexFound;
72+
while ( i >= 0 && entries.get(i).timestamp.equals(entry.timestamp)) {
73+
i--;
74+
}
75+
76+
// Now that we are in the beginning of the timestamp, look for the entry
77+
while (!entries.get(i).logText.equals(entry.logText) &&
78+
entries.get(i).timestamp.compareTo(entry.timestamp) <= 0) {
79+
i++;
80+
}
81+
82+
// We either found or finished search. check which one
83+
if (entries.get(i).logText.equals(entry.logText)) {
84+
return entries.get(i);
85+
}
6586
}
66-
}
67-
68-
public void removeLog(LogEntry entry) {
69-
int index = this.entries.indexOf(entry);
70-
if (index != -1) {
71-
this.entries.remove(index);
72-
fireTableRowsDeleted(index, index);
73-
}
74-
}
75-
76-
public void clearLog() {
77-
if (this.entries.isEmpty()) return;
7887

79-
int index = this.entries.size() - 1;
80-
this.entries.clear();
81-
fireTableRowsDeleted(0, index);
88+
// Not found
89+
return null;
8290
}
8391
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.tibagni.logviewer.log
2+
3+
import org.junit.Assert.*
4+
import org.junit.Test
5+
6+
class LogEntryTests {
7+
8+
@Test
9+
fun testLogEntryEquals() {
10+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
11+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
12+
13+
assertEquals(entry1, entry2)
14+
assertEquals(entry1.hashCode(), entry2.hashCode())
15+
}
16+
17+
@Test
18+
fun testLogEntryTextNotEquals() {
19+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
20+
val entry2 = LogEntry("Text2", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
21+
22+
assertNotEquals(entry1, entry2)
23+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
24+
}
25+
26+
@Test
27+
fun testLogEntryLevelNotEquals() {
28+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
29+
val entry2 = LogEntry("Text1", LogLevel.INFO, LogTimestamp(9, 1, 8, 0, 0, 0))
30+
31+
assertNotEquals(entry1, entry2)
32+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
33+
}
34+
35+
@Test
36+
fun testLogEntryTimestampMonthNotEquals() {
37+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
38+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(8, 1, 8, 0, 0, 0))
39+
40+
assertNotEquals(entry1, entry2)
41+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
42+
}
43+
44+
@Test
45+
fun testLogEntryTimestampDayNotEquals() {
46+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
47+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 0, 0, 0))
48+
49+
assertNotEquals(entry1, entry2)
50+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
51+
}
52+
53+
@Test
54+
fun testLogEntryTimestampHourNotEquals() {
55+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
56+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 3, 0, 0, 0))
57+
58+
assertNotEquals(entry1, entry2)
59+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
60+
}
61+
62+
@Test
63+
fun testLogEntryTimestampMinutesNotEquals() {
64+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
65+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 10, 0, 0))
66+
67+
assertNotEquals(entry1, entry2)
68+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
69+
}
70+
71+
@Test
72+
fun testLogEntryTimestampSecondsNotEquals() {
73+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
74+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 0, 30, 0))
75+
76+
assertNotEquals(entry1, entry2)
77+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
78+
}
79+
80+
@Test
81+
fun testLogEntryTimestampHundredthNotEquals() {
82+
val entry1 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 1, 8, 0, 0, 0))
83+
val entry2 = LogEntry("Text1", LogLevel.DEBUG, LogTimestamp(9, 2, 8, 0, 0, 90))
84+
85+
assertNotEquals(entry1, entry2)
86+
assertNotEquals(entry1.hashCode(), entry2.hashCode())
87+
}
88+
}

0 commit comments

Comments
 (0)