Skip to content

Commit

Permalink
Refatctor 'My Logs' and create tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tibagni committed Oct 9, 2023
1 parent f6a525d commit 3afdd17
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 138 deletions.
3 changes: 3 additions & 0 deletions src/main/java/com/tibagni/logviewer/LogViewerPresenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,8 @@ enum UserSelection {
LogEntry getFirstVisibleLog();
LogEntry getLastVisibleLog();

void addLogEntriesToMyLogs(List<LogEntry> entries);
void removeFromMyLog(int[] indices);

void finishing();
}
81 changes: 80 additions & 1 deletion src/main/java/com/tibagni/logviewer/LogViewerPresenterImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,19 @@ public class LogViewerPresenterImpl extends AsyncPresenter implements LogViewerP
private final LogViewerPreferences userPrefs;

private final LogsRepository logsRepository;
private final MyLogsRepository myLogsRepository;
private final FiltersRepository filtersRepository;

LogViewerPresenterImpl(LogViewerPresenterView view,
LogViewerPreferences userPrefs,
LogsRepository logsRepository,
MyLogsRepository myLogsRepository,
FiltersRepository filtersRepository) {
super(view);
this.view = view;
this.userPrefs = userPrefs;
this.logsRepository = logsRepository;
this.myLogsRepository = myLogsRepository;
this.filtersRepository = filtersRepository;

unsavedFilterGroups = new ArrayList<>();
Expand Down Expand Up @@ -371,7 +374,7 @@ public void loadFilters(File[] filtersFiles, boolean keepCurrentFilters) {
if (isLegacy) {
Logger.info("Filter Group: " + group + " is using old file format. Re-save it");
// Now that we checked, clear the legacy flag
filters.stream().forEach(filter -> filter.wasLoadedFromLegacyFile = false);
filters.forEach(filter -> filter.wasLoadedFromLegacyFile = false);
// ... and save it
saveFilters(group);
}
Expand Down Expand Up @@ -404,10 +407,15 @@ public void loadLogs(File[] logFiles) {

List<String> skippedLogs = logsRepository.getLastSkippedLogFiles();
Map<String, String> bugReports = logsRepository.getPotentialBugReports();
final boolean myLogsChanged = updateMyLogs();
doOnUiThread(() -> {
view.showFilteredLogs(cachedAllowedFilteredLogs);
view.showLogs(logsRepository.getCurrentlyOpenedLogs());
view.showAvailableLogStreams(allowedStreamsMap.keySet());
// In case something changed with 'My Logs', make sure to update the UI as well
if (myLogsChanged) {
view.showMyLogs(myLogsRepository.getLogs());
}

if (logsRepository.getCurrentlyOpenedLogs().size() > 0) {
String logsPath = FilenameUtils.getFullPath(logFiles[0].getAbsolutePath());
Expand Down Expand Up @@ -648,6 +656,77 @@ public LogEntry getLastVisibleLog() {
return logsRepository.getCurrentlyOpenedLogs().get(lastVisibleIndex);
}

@Override
public void addLogEntriesToMyLogs(List<LogEntry> entries) {
myLogsRepository.addLogEntries(entries);
view.showMyLogs(myLogsRepository.getLogs());
}

@Override
public void removeFromMyLog(int[] indices) {
List<LogEntry> toRemove = new ArrayList<>();
for (int i : indices) {
int myLogsSize = myLogsRepository.getLogs().size();
if (i < 0 || i >= myLogsSize) {
Logger.warning("Trying to remove invalid index " + i + " from MyLogs. Current size: " + myLogsSize);
} else {
toRemove.add(myLogsRepository.getLogs().get(i));
}
}
myLogsRepository.removeLogEntries(toRemove);
view.showMyLogs(myLogsRepository.getLogs());
}

boolean updateMyLogs() {
// We only want to update 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 (myLogsRepository.getLogs().isEmpty()) {
return false;
}

boolean mismatch = false;
for (LogEntry myLogEntry : myLogsRepository.getLogs()) {
// If myLog's index is greater than the number of new logs, it is a mismatch
if (myLogEntry.getIndex() > logsRepository.getLastVisibleLogIndex()) {
mismatch = true;
break;
}

// If the myLog no longer matches the same index in the new logs, it is a mismatch
List<LogEntry> currentLogs = logsRepository.getCurrentlyOpenedLogs();
if (myLogEntry.getIndex() >= 0 && myLogEntry.getIndex() < currentLogs.size() &&
!myLogEntry.equals(currentLogs.get(myLogEntry.getIndex()))) {
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'
List<LogEntry> matchedLogs = new ArrayList<>();

for (LogEntry myLogEntry : myLogsRepository.getLogs()) {
LogEntry matchedLogEntry = logsRepository.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' to have the correct indices so, remove all and
// reinsert the ones that have a match
myLogsRepository.reset(matchedLogs);
return true;
}

return false;
}

private List<LogEntry> excludeNonAllowedStreams(List<LogEntry> entries) {
if (allowedStreamsMap.isEmpty()) {
// If there is no stream restriction just work with all entries
Expand Down
88 changes: 19 additions & 69 deletions src/main/java/com/tibagni/logviewer/LogViewerView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface LogViewerPresenterView : AsyncPresenter.AsyncPresenterView {
fun showErrorMessage(message: String?)
fun showSkippedLogsMessage(skippedLogs: List<String>)
fun showLogs(logEntries: List<LogEntry>?)
fun showMyLogs(logEntries: List<LogEntry>?)
fun showCurrentLogsLocation(logsPath: String?)
fun showFilteredLogs(logEntries: List<LogEntry>?)
fun showLogLocationAtSearchedTimestamp(allLogsPosition: Int, filteredLogsPosition: Int)
Expand Down Expand Up @@ -116,7 +117,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil

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

Expand All @@ -135,6 +136,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
this,
userPrefs,
ServiceLocator.logsRepository,
ServiceLocator.myLogsRepository,
ServiceLocator.filtersRepository
)
presenter.init()
Expand All @@ -152,8 +154,8 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
override fun onShowLineNumbersChanged() {
logRenderer.showLineNumbers(userPrefs.showLineNumbers)
myLogsRenderer.showLineNumbers(userPrefs.showLineNumbers)
logList.revalidate()
logList.repaint()
logList.table.revalidate()
logList.table.repaint()
filteredLogList.table.revalidate()
filteredLogList.table.repaint()
myLogsList.table.revalidate()
Expand Down Expand Up @@ -437,11 +439,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
popup
.add("Add to 'My Logs'")
.addActionListener {
selectedRows
.map { model.getValueAt(it, 0) as LogEntry }
.forEach { myLogsListTableModel.addLogIfDoesNotExist(it) }
sidePanel.showMyLogsView()
myLogsListTableModel.lastEntry?.let { myLogsRenderer.recalculateLineNumberPreferredSize(it.index) }
presenter.addLogEntriesToMyLogs(selectedRows.map { model.getValueAt(it, 0) as LogEntry })
}

if (selectedRows.size == 1) {
Expand Down Expand Up @@ -508,13 +506,6 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
}

private fun setupMyLogsContextActions() {
val deleteSelectedFromMyLogs: () -> Unit = {
myLogsList.table.selectedRows
.map { myLogsListTableModel.getValueAt(it, 0) as LogEntry }
.forEach { myLogsListTableModel.removeLog(it) }
myLogsListTableModel.lastEntry?.let { myLogsRenderer.recalculateLineNumberPreferredSize(it.index) }
}

myLogsList.table.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.clickCount == 2) {
Expand All @@ -541,7 +532,7 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
val popup = JPopupMenu()
val removeItem = popup.add("Remove")
removeItem.addActionListener {
deleteSelectedFromMyLogs()
presenter.removeFromMyLog(myLogsList.table.selectedRows)
}
popup.show(myLogsList.table, e.x, e.y)
}
Expand All @@ -551,62 +542,12 @@ class LogViewerViewImpl(private val mainView: MainView, initialLogFiles: Set<Fil
myLogsList.table.addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent?) {
if (myLogsList.table.selectedRow != -1 && (e?.keyCode == KeyEvent.VK_DELETE)) {
deleteSelectedFromMyLogs()
presenter.removeFromMyLog(myLogsList.table.selectedRows)
}
}
})
}

// 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 @@ -752,7 +693,16 @@ 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 showMyLogs(logEntries: List<LogEntry>?) {
if (logEntries.isNullOrEmpty()) {
myLogsListTableModel.clear()
} else {
myLogsListTableModel.setLogs(logEntries)
myLogsListTableModel.lastEntry?.let { myLogsRenderer.recalculateLineNumberPreferredSize(it.index) }
sidePanel.showMyLogsView()
}
}

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

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

mainLogSplit.leftComponent = logsPane
Expand Down
39 changes: 35 additions & 4 deletions src/main/java/com/tibagni/logviewer/LogsRepository.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.tibagni.logviewer

import com.tibagni.logviewer.log.FileLogReader
import com.tibagni.logviewer.log.LogEntry
import com.tibagni.logviewer.log.LogReaderException
import com.tibagni.logviewer.log.LogStream
import com.tibagni.logviewer.log.*
import com.tibagni.logviewer.log.parser.LogParser
import com.tibagni.logviewer.logger.wrapProfiler
import java.io.File
import java.util.*

class OpenLogsException(message: String?, cause: Throwable) : java.lang.Exception(message, cause)

Expand All @@ -27,6 +25,7 @@ interface LogsRepository {

@Throws(OpenLogsException::class)
fun openLogFiles(files: Array<File>, progressReporter: ProgressReporter)
fun getMatchingLogEntry(entry: LogEntry): LogEntry?
}

class LogsRepositoryImpl : LogsRepository {
Expand Down Expand Up @@ -97,4 +96,36 @@ class LogsRepositoryImpl : LogsRepository {
}
}
}

override fun getMatchingLogEntry(entry: LogEntry): LogEntry? {
// Here we want to check if 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
val cmp = Comparator.comparing { o: LogEntry -> o.timestamp }

val indexFound = Collections.binarySearch(currentlyOpenedLogs, 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
var i = indexFound
while (i >= 0 && currentlyOpenedLogs[i].timestamp == entry.timestamp) {
i--
}

// Now that we are in the beginning of the timestamp, look for the entry
while (currentlyOpenedLogs[i].logText != entry.logText &&
currentlyOpenedLogs[i].timestamp <= entry.timestamp) {
i++
}

// We either found or finished search. check which one
if (currentlyOpenedLogs[i].logText == entry.logText) {
return currentlyOpenedLogs[i]
}
}

// Not found
return null
}
}
Loading

0 comments on commit 3afdd17

Please sign in to comment.