/* * sulky-modules - several general-purpose modules. * Copyright (C) 2007-2015 Joern Huxhorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright 2007-2015 Joern Huxhorn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.huxhorn.sulky.buffers.table; import de.huxhorn.sulky.buffers.Buffer; import de.huxhorn.sulky.buffers.CircularBuffer; import de.huxhorn.sulky.buffers.Dispose; import de.huxhorn.sulky.buffers.DisposeOperation; import de.huxhorn.sulky.buffers.Reset; import de.huxhorn.sulky.io.IOUtilities; import de.huxhorn.sulky.swing.RowBasedTableModel; import java.awt.EventQueue; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.event.EventListenerList; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class BufferTableModel<T> implements RowBasedTableModel<T>, DisposeOperation { private final Logger logger = LoggerFactory.getLogger(BufferTableModel.class); private Buffer<T> buffer; private CircularBuffer<T> circularBuffer; private final EventListenerList eventListenerList; private final AtomicBoolean disposed=new AtomicBoolean(); private final AtomicBoolean paused=new AtomicBoolean(); private int pauseRowCount; private int lastRowCount; public BufferTableModel(Buffer<T> buffer) { eventListenerList = new EventListenerList(); lastRowCount = 0; disposed.set(false); setBuffer(buffer); Thread t = new Thread(new TableChangeDetectionRunnable(), "TableChangeDetection"); t.setDaemon(true); t.setPriority(Thread.NORM_PRIORITY - 1); t.start(); setPaused(false); } public synchronized boolean isPaused() { return paused.get(); } public synchronized void setPaused(boolean paused) { if(paused) { pauseRowCount = getRowCount(); } this.paused.set(paused); notifyAll(); } public Buffer<T> getBuffer() { return buffer; } public void setBuffer(Buffer<T> buffer) { this.buffer = buffer; if(buffer instanceof CircularBuffer) { this.circularBuffer = (CircularBuffer<T>) buffer; } else { this.circularBuffer = null; } setLastRowCount(0); this.pauseRowCount = 0; fireTableChange(); } public boolean clear() { boolean reset = Reset.reset(buffer); if(reset) { setLastRowCount(0); fireTableChange(); return true; } return false; } public synchronized int getRowCount() { return lastRowCount; } private synchronized void setLastRowCount(int lastRowCount) { this.lastRowCount = lastRowCount; } public synchronized void dispose() { disposed.set(true); Dispose.dispose(buffer); notifyAll(); } public synchronized boolean isDisposed() { return disposed.get(); } private int internalRowCount() { if(isPaused()) { return pauseRowCount; } if(circularBuffer != null) { // special circular handling return circularBuffer.getAvailableElements(); } long rows = buffer.getSize(); if(rows > Integer.MAX_VALUE) { if(logger.isWarnEnabled()) logger.warn("Swing can only handle {} rows instead of {}!", Integer.MAX_VALUE, rows); rows = Integer.MAX_VALUE; } return (int) rows; } public T getValueAt(int row) { if(circularBuffer != null) { // special circular handling return circularBuffer.getRelative(row); } return buffer.get(row); } public abstract int getColumnCount(); public abstract String getColumnName(int columnIndex); public abstract Class<?> getColumnClass(int columnIndex); public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public Object getValueAt(int rowIndex, int columnIndex) { T value = getValueAt(rowIndex); if(logger.isDebugEnabled()) logger.debug("value: {}", value); return value; } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { } private void fireTableChange() { TableModelEvent event = new TableModelEvent(this); fireTableChange(event); } private void fireTableChange(int prevValue, int currentValue) { TableModelEvent event = new TableModelEvent(this, prevValue, currentValue, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT); fireTableChange(event); } private void fireTableChange(TableModelEvent evt) { Runnable r = new BufferTableModel.FireTableChangeRunnable(evt); if(EventQueue.isDispatchThread()) { r.run(); } else { EventQueue.invokeLater(r); } } private class FireTableChangeRunnable implements Runnable { private TableModelEvent event; FireTableChangeRunnable(TableModelEvent event) { this.event = event; } public void run() { Object[] listeners; synchronized(eventListenerList) { listeners = eventListenerList.getListenerList(); } // Process the listeners last to first, notifying // those that are interested in this event for(int i = listeners.length - 2; i >= 0; i -= 2) { if(listeners[i] == TableModelListener.class) { TableModelListener listener = ((TableModelListener) listeners[i + 1]); if(logger.isDebugEnabled()) { logger.debug("Firing TableChange at {}.", listener.getClass().getName()); } try { listener.tableChanged(event); } catch(Throwable ex) { if(logger.isWarnEnabled()) logger.warn("Exception while firing change!", ex); IOUtilities.interruptIfNecessary(ex); } } } } } public void addTableModelListener(TableModelListener l) { synchronized(eventListenerList) { eventListenerList.add(TableModelListener.class, l); } } public void removeTableModelListener(TableModelListener l) { synchronized(eventListenerList) { eventListenerList.remove(TableModelListener.class, l); } } class TableChangeDetectionRunnable implements Runnable { private static final int UPDATE_INTERVAL = 500; public void run() { for(;;) { if(isDisposed()) { if(logger.isDebugEnabled()) logger.debug("Stopping TableChangeDetectionRunnable..."); return; } if(!isPaused()) { int currentValue = internalRowCount(); if(currentValue > -1) { int prevValue = getRowCount(); if(prevValue != 0 && currentValue > prevValue) { int lastRow = currentValue - 1; setLastRowCount(currentValue); fireTableChange(prevValue, lastRow); } else if(currentValue != prevValue) { setLastRowCount(currentValue); fireTableChange(); } } try { Thread.sleep(UPDATE_INTERVAL); } catch(InterruptedException e) { if(logger.isDebugEnabled()) logger.debug("Interrupted...", e); IOUtilities.interruptIfNecessary(e); return; } continue; } synchronized(BufferTableModel.this) { for(;;) { if(!isPaused()) { break; } try { BufferTableModel.this.wait(); } catch(InterruptedException e) { if(logger.isDebugEnabled()) logger.debug("Interrupted...", e); IOUtilities.interruptIfNecessary(e); return; } } } } } } }