/** * Logback-beagle: The logback Console Plugin for Eclipse * Copyright (C) 2006-2012, QOS.ch. All rights reserved. * * This program and the accompanying materials are licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ package ch.qos.logback.beagle.visual; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.nebula.widgets.grid.Grid; import org.eclipse.nebula.widgets.grid.GridItem; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import ch.qos.logback.beagle.tree.LoggerTree; import ch.qos.logback.beagle.util.ResourceUtil; import ch.qos.logback.beagle.view.ConverterFacade; import ch.qos.logback.beagle.view.TableMediator; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.core.helpers.CyclicBuffer; public class ClassicTISBuffer implements ITableItemStubBuffer<ILoggingEvent>, Listener, DisposeListener { static ITableItemStub[] EMPTY_TIS_ARRAY = new ITableItemStub[0]; List<ITableItemStub> tisList = Collections .synchronizedList(new ArrayList<ITableItemStub>()); private int lineCount = 0; private TableMediator tableMediator; private final Grid grid; private final ConverterFacade converterFacade; private final Display display; private boolean scrollingEnabled = true; private volatile boolean disposed = false; private int bufferSize; private int minDropSize; private CyclicBuffer<ILoggingEvent> cyclicBuffer; private LoggerContext loggerContext; public ClassicTISBuffer(TableMediator tableMediator, int bufferSize) { this.tableMediator = tableMediator; this.grid = tableMediator.getGrid(); this.display = grid.getDisplay(); this.loggerContext = tableMediator.getLoggerContext(); this.converterFacade = tableMediator.getConverterFacade(); this.bufferSize = bufferSize; this.minDropSize = computeMinDropSize(bufferSize); this.cyclicBuffer = new CyclicBuffer<ILoggingEvent>(bufferSize); } public ConverterFacade getConverterFacade() { return converterFacade; } private int computeMinDropSize(int aBufferSize) { return Math.min(1024, aBufferSize / 10); } /** * Set the buffer size to new value. Ig the new buffer size is smaller then * the old size, the buffer will reach the smaller size in multiple steps. * * @param size */ public void setBufferSize(int size) { this.bufferSize = size; this.minDropSize = computeMinDropSize(bufferSize); } /** * This method is called when a tableItem needs to be refreshed. */ public void handleEvent(Event event) { if (event.type != SWT.SetData) { throw new IllegalStateException("Unexpected event type " + event.type); } GridItem item = (GridItem) event.item; int index = event.index; // ignore out of bounds requests if (index >= tisList.size()) { return; } ITableItemStub tis = tisList.get(index); tis.populate(item); } /** * Convert an ILoggingEvent to a list of IVisualElement. * * @param tisList * @param event */ private void loggingEventToVisualElement(List<ITableItemStub> aTISList, ILoggingEvent event) { lineCount++; Color c = null; if (lineCount % 2 == 0) { c = ResourceUtil.GRAY; } aTISList.add(new LoggingEventTIS(converterFacade, event, c)); IThrowableProxy tp = event.getThrowableProxy(); while (tp != null) { IThrowableProxy itp = event.getThrowableProxy(); aTISList.add(new ThrowableProxyTIS(converterFacade, itp, ThrowableProxyTIS.INDEX_FOR_INITIAL_LINE, c)); int stackDepth = itp.getStackTraceElementProxyArray().length; for (int i = 0; i < stackDepth; i++) { aTISList.add(new ThrowableProxyTIS(converterFacade, itp, i, c)); } if (itp.getCommonFrames() > 0) { aTISList.add(new ThrowableProxyTIS(converterFacade, itp, stackDepth, c)); } tp = tp.getCause(); } } static int MAX_EXTENT = 20; private int findIndexOfFirstCallerItem(int middleIndex) { int limit = middleIndex - MAX_EXTENT >= 0 ? middleIndex - MAX_EXTENT : 0; int found = middleIndex; for (int i = middleIndex; i >= limit; i--) { if (tisList.get(i) instanceof CallerDataTIS) found = i; else break; } return found; } private int findIndexOfLastCallerItem(int middleIndex) { int limit = middleIndex + MAX_EXTENT <= tisList.size() ? middleIndex + MAX_EXTENT : tisList.size(); int found = middleIndex; for (int i = middleIndex; i < limit; i++) { if (tisList.get(i) instanceof CallerDataTIS) found = i; else break; } return found; } public void removeNeighboringItems(int index) { int beginIndex = findIndexOfFirstCallerItem(index); int lastIndex = findIndexOfLastCallerItem(index); tisList.subList(beginIndex, lastIndex + 1).clear(); grid.remove(beginIndex, lastIndex); assertSize(); display.syncExec(new RefreshGridRunnable()); } public void clearAll() { cyclicBuffer.clear(); clearGridAndUnderlyingList(); } private void clearGridAndUnderlyingList() { tisList.clear(); grid.removeAll(); } public void refreshGrid() { display.syncExec(new RefreshGridRunnable()); } public void addAtIndex(ITableItemStub iTableItemStub, int index) { tisList.add(index, iTableItemStub); refreshGrid(); } private boolean filterEvent(ILoggingEvent event) { Logger logger = loggerContext.getLogger(event.getLoggerName()); return logger.isEnabledFor(event.getLevel()); } /** * This method is invoked by the producer to add events into this buffer/list. * * @param loggingEventList */ public void add(final List<ILoggingEvent> loggingEventList) { List<ITableItemStub> itemStubList = new ArrayList<ITableItemStub>(); Set<String> loggerNames = new HashSet<String>(); for (ILoggingEvent iLoggingEvent : loggingEventList) { cyclicBuffer.add(iLoggingEvent); loggerNames.add(iLoggingEvent.getLoggerName()); if (filterEvent(iLoggingEvent)) { loggingEventToVisualElement(itemStubList, iLoggingEvent); } } addGridItemStubs(itemStubList, loggerNames); } public void add(final ILoggingEvent iLoggingEvent) { List<ITableItemStub> visualElementList = new ArrayList<ITableItemStub>(); Set<String> loggerNames = new HashSet<String>(); loggingEventToVisualElement(visualElementList, iLoggingEvent); loggerNames.add(iLoggingEvent.getLoggerName()); addGridItemStubs(visualElementList, loggerNames); } private void addGridItemStubs(final List<ITableItemStub> newTISList, Set<String> loggerNames) { if (disposed) return; for (ITableItemStub ve : newTISList) tisList.add(ve); display.syncExec(new AddTableItemRunnable(newTISList, loggerNames)); contactIfTooBig(); } private void contactIfTooBig() { int extraneousElementCount = this.tisList.size() - bufferSize; if (extraneousElementCount > 0) { int elementsToDrop = minDropSize+extraneousElementCount; this.tisList.subList(0, elementsToDrop).clear(); display.syncExec(new AdjustGridPostContractionRunnable(elementsToDrop)); } } public void rebuildEmptyGrid() { display.syncExec(new RebuildEmptyGridRunnable()); } public int size() { return this.tisList.size(); } public ITableItemStub get(int index) { if (index >= this.tisList.size()) { return null; } else { return this.tisList.get(index); } } @Override public void widgetDisposed(DisposeEvent e) { disposed = true; } public Grid getGrid() { return grid; } public boolean isScrollingEnabled() { return scrollingEnabled; } public void setScrollingEnabled(boolean scrollingEnabled) { this.scrollingEnabled = scrollingEnabled; } // called by display.syncExec!! private void addNewItemStubsToGrid(List<ITableItemStub> newTableItemStubs) { if (disposed || newTableItemStubs.size() == 0) { return; } GridItem lastGridItem = null; for (ITableItemStub tis : newTableItemStubs) { lastGridItem = new GridItem(grid, SWT.NONE); if (disposed) return; tis.populate(lastGridItem); } tableMediator.setTotalEventsLabelText(this.tisList.size() + " events"); if (isScrollingEnabled()) { grid.showItem(lastGridItem); } } void assertSize() { if (grid.getItemCount() != tisList.size()) { System.out.println("size mismatch gridSize=" + grid.getItemCount() + " tisListSize" + tisList.size()); } } public void handleChangeInFilters() { clearGridAndUnderlyingList(); for (ILoggingEvent iLoggingEvent : cyclicBuffer.asList()) { if (filterEvent(iLoggingEvent)) loggingEventToVisualElement(tisList, iLoggingEvent); } rebuildEmptyGrid(); } // ------------------------- RefreshGridRunnable class private final class RefreshGridRunnable implements Runnable { public void run() { grid.clearAll(true); grid.setItemCount(tisList.size()); } } // ------------------------- AdjustGridPostContractionRunnable class private final class AdjustGridPostContractionRunnable implements Runnable { final int elementsToDrop; public AdjustGridPostContractionRunnable(int elementsToDrop) { this.elementsToDrop = elementsToDrop; } public void run() { int topIndex = grid.getTopIndex(); grid.remove(0, elementsToDrop - 1); assertSize(); grid.setTopIndex(topIndex - elementsToDrop); } } // ------------------------- AddTableItemRunnable class private final class AddTableItemRunnable implements Runnable { private final List<ITableItemStub> aTISList; private final Set<String> loggerNames; private AddTableItemRunnable(List<ITableItemStub> aTISList, Set<String> loggerNames) { this.aTISList = aTISList; this.loggerNames = loggerNames; } public void run() { addNewItemStubsToGrid(aTISList); LoggerTree lt = tableMediator.getLoggerTree(); for (String loggerName : loggerNames) { lt.update(loggerName); } } } // ------------------------- RebuildEmptyGridRunnable class private final class RebuildEmptyGridRunnable implements Runnable { public void run() { addNewItemStubsToGrid(ClassicTISBuffer.this.tisList); } } }