Skip to content

Commit

Permalink
Clear 'My Logs' if it does not match the new set of open logs
Browse files Browse the repository at this point in the history
  • Loading branch information
tibagni committed Sep 1, 2023
1 parent ad97de7 commit f6a525d
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 26 deletions.
57 changes: 55 additions & 2 deletions src/main/java/com/tibagni/logviewer/LogViewerView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -114,7 +116,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil

private lateinit var logListTableModel: LogListTableModel
private lateinit var filteredLogListTableModel: LogListTableModel
private lateinit var myLogsListTableModel: LogListTableModel
private lateinit var myLogsListTableModel: EditableLogListTableModel
private val logRenderer: LogCellRenderer
private val myLogsRenderer: LogCellRenderer

Expand Down Expand Up @@ -555,6 +557,56 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
})
}

// TODO Move 'My Logs' handling logic to the presenter/repository
private fun clearMyLogsIfNeeded() {
// We only want to clear logs in 'My Logs' if the new logs that are being loaded are different
// So we check if the logs under 'My Logs' are still matching the logs that are open. If not, clear
// This is to avoid clearing 'My Logs' when the user is simply refreshing the logs (F5)
if (myLogsListTableModel.rowCount == 0) {
return
}

var mismatch = false
for (i in 0 until myLogsListTableModel.rowCount) {
val myLogEntry = myLogsListTableModel.getValueAt(i, 0) as LogEntry

// If myLog's index is greater than the number of new logs, it is a mismatch
if (myLogEntry.index >= 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<LogEntry>()

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

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

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

myLogsListTableModel = LogListTableModel("My Logs")
myLogsListTableModel = EditableLogListTableModel("My Logs")
myLogsList = SearchableTable(myLogsListTableModel)

mainLogSplit.leftComponent = logsPane
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.tibagni.logviewer.log

import java.util.*
import kotlin.math.abs

class EditableLogListTableModel(title: String) : LogListTableModel(title) {

fun addLogIfDoesNotExist(entry: LogEntry) {
// find the nearest time pos to insert the new entry
val indexFound = Collections.binarySearch(entries, entry)
if (indexFound < 0) { // Element not found. Insert
val targetIndex = abs(indexFound + 1)
entries.add(targetIndex, entry)
fireTableRowsInserted(targetIndex, targetIndex)
}
}

fun removeLog(entry: LogEntry) {
val index = entries.indexOf(entry)
if (index != -1) {
entries.removeAt(index)
fireTableRowsDeleted(index, index)
}
}

fun clear() {
if (entries.isEmpty()) return
val index = entries.size - 1
entries.clear()
fireTableRowsDeleted(0, index)
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/tibagni/logviewer/log/LogEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;

public class LogEntry implements Comparable<LogEntry> {

private int index;
Expand Down Expand Up @@ -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);
}
}
56 changes: 32 additions & 24 deletions src/main/java/com/tibagni/logviewer/log/LogListTableModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<LogEntry> entries = new ArrayList<>();
private final String title;
protected final List<LogEntry> entries = new ArrayList<>();
protected final String title;

public LogListTableModel(String title) {
this.title = title;
Expand Down Expand Up @@ -55,29 +56,36 @@ public void setLogs(List<LogEntry> 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<LogEntry> 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;
}
}
88 changes: 88 additions & 0 deletions src/test/java/com/tibagni/logviewer/log/LogEntryTests.kt
Original file line number Diff line number Diff line change
@@ -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())
}
}

0 comments on commit f6a525d

Please sign in to comment.