All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xdev.ui.paging.LazyLoadingTableModel Maven / Gradle / Ivy

/*
 * XDEV Application Framework - XDEV Application Framework
 * Copyright © 2003 XDEV Software (https://xdev.software)
 *
 * 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 .
 */
package xdev.ui.paging;


import java.awt.Cursor;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterEvent.Type;
import javax.swing.event.RowSorterListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

import xdev.db.DBException;
import xdev.db.DBPager;
import xdev.db.QueryInfo;
import xdev.db.Result;
import xdev.db.sql.SELECT;
import xdev.ui.MasterDetailComponent;
import xdev.util.logging.LoggerFactory;
import xdev.util.logging.XdevLogger;
import xdev.vt.VirtualTable;
import xdev.vt.VirtualTable.VirtualTableRow;
import xdev.vt.VirtualTableColumn;
import xdev.vt.VirtualTableFillMethod;
import xdev.vt.VirtualTableModel;
import xdev.vt.VirtualTableWrapper;


/**
 * {@link VirtualTable}-based {@link TableModel} which uses a {@link DBPager} to
 * lazy load the viewed rows.
 * 
 * @author XDEV Software
 * 
 * @since 4.0
 */
public class LazyLoadingTableModel> extends
		AbstractTableModel implements VirtualTableWrapper
{
	
	public static final XdevLogger	log				= LoggerFactory
															.getLogger(LazyLoadingTableModel.class);
	
	private T						table;
	private VirtualTable			vt;
	private int[]					columnIndices;
	private SELECT					select;
	private Object[]				params;
	private DBPager					pager;
	
	private int						totalRowCount	= -1;
	private Timer					loadTimer;
	private ThreadPoolExecutor		executor;
	private SortKey					sortKey;
	private int						requestedFirst	= -1;
	
	private int						requestedLast	= -1;
	private int						loadedFirst		= -1;
	private int						loadedLast		= -1;
	
	
	public LazyLoadingTableModel(T t)
	{
		table = t;
		table.addHierarchyListener(new HierarchyListener()
		{
			@Override
			public void hierarchyChanged(HierarchyEvent e)
			{
				if(e.getID() == HierarchyEvent.HIERARCHY_CHANGED && !table.isShowing())
				{
					table.removeHierarchyListener(this);
					
					if(pager != null)
					{
						try
						{
							pager.close();
						}
						catch(DBException ex)
						{
							handle(ex);
						}
					}
				}
			}
		});
		
		loadTimer = new Timer(250,new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				synchronized(LazyLoadingTableModel.this)
				{
					final int first = requestedFirst;
					final int last = requestedLast;
					executor.execute(new Runnable()
					{
						@Override
						public void run()
						{
							try
							{
								loadRequestedData(first,last);
							}
							catch(Exception e)
							{
								handle(e);
							}
						}
					});
				}
			}
		});
		loadTimer.setRepeats(false);
		
		executor = new ThreadPoolExecutor(0,1,10L,TimeUnit.SECONDS,
				new LinkedBlockingQueue());
	}
	
	
	public void setData(VirtualTable vt, int[] columnIndices, SELECT select, Object[] params)
	{
		boolean needsRefresh = this.vt != null;
		
		this.vt = vt;
		this.columnIndices = columnIndices;
		this.select = select;
		this.params = params;
		totalRowCount = -1;
		
		if(needsRefresh)
		{
			refresh();
		}
	}
	
	
	public void clear()
	{
		totalRowCount = 0;
		if(vt != null)
		{
			vt.clear();
		}
		fireTableDataChanged();
	}
	
	
	public void refresh()
	{
		requestedFirst = requestedLast = loadedFirst = loadedLast = -1;
		
		if(sortKey != null)
		{
			int viewIndex = sortKey.getColumn();
			int sortindex = columnIndices[viewIndex];
			VirtualTableColumn col = vt.getColumnAt(sortindex);
			select.clear_ORDER_BY();
			select.ORDER_BY(col,sortKey.getSortOrder() == SortOrder.DESCENDING);
		}
		
		try
		{
			if(pager != null)
			{
				pager.close();
			}
			pager = getVirtualTable().getDataSource().openConnection().getPager(10,select,params);
		}
		catch(DBException e)
		{
			handle(e);
		}
		
		SwingUtilities.invokeLater(new Runnable()
		{
			@Override
			public void run()
			{
				fireTableDataChanged();
			}
		});
	}
	
	
	public RowSorter createRowSorter(TableModel model)
	{
		TableRowSorter sorter = new TableRowSorter(model)
		{
			@Override
			public void sort()
			{
				fireRowSorterChanged(null);
			}
		};
		
		sorter.addRowSorterListener(new RowSorterListener()
		{
			int	call	= 1;
			
			
			@Override
			public void sorterChanged(RowSorterEvent e)
			{
				if(call % 2 == 0)
				{
					Type t = e.getType();
					if(t == Type.SORTED && call != 2)
					{
						return;
					}
					List l = e.getSource().getSortKeys();
					if(l.isEmpty())
					{
						return;
					}
					sortKey = (SortKey)l.get(0);
					
					synchronized(LazyLoadingTableModel.this)
					{
						refresh();
					}
				}
				call++;
			}
		});
		
		return sorter;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getRowCount()
	{
		if(totalRowCount == -1)
		{
			totalRowCount = 0;
			
			executor.execute(new Runnable()
			{
				@Override
				public void run()
				{
					fetchTotalRowCount();
				}
			});
		}
		
		return totalRowCount;
	}
	
	
	private void fetchTotalRowCount()
	{
		try
		{
			pager = getVirtualTable().getDataSource().openConnection().getPager(10,select,params);
			totalRowCount = pager.getTotalRows();
			
			SwingUtilities.invokeLater(new Runnable()
			{
				@Override
				public void run()
				{
					fireTableDataChanged();
				}
			});
		}
		catch(DBException e)
		{
			handle(e);
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object getValueAt(int row, int col)
	{
		synchronized(this)
		{
			if(row < loadedFirst || row > loadedLast)
			{
				if(row < requestedFirst || row > requestedLast)
				{
					Rectangle vr = table.getVisibleRect();
					int requestedFirst = table.rowAtPoint(vr.getLocation());
					vr.translate(0,vr.height);
					int requestedLast = table.rowAtPoint(vr.getLocation());
					
					if(requestedLast == -1 || requestedLast >= totalRowCount)
					{
						// end of table
						requestedLast = totalRowCount - 1;
					}
					
					if(this.requestedFirst != requestedFirst || this.requestedLast != requestedLast)
					{
						this.requestedFirst = requestedFirst;
						this.requestedLast = requestedLast;
						
						loadTimer.restart();
					}
				}
				
				return " ";
			}
		}
		
		return vt.getValueAt(row - loadedFirst,columnIndices[col]);
	}
	
	
	private void loadRequestedData(int requestedFirst, int requestedLast) throws Exception
	{
		if(pager != null)
		{
			if(pager.isClosed())
			{
				refresh();
				return;
			}
			
			try
			{
				table.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
				
				pager.setRowsPerPage(requestedLast - requestedFirst + 1);
				Result result = pager.gotoRow(requestedFirst);
				QueryInfo lastQuery = vt.getLastQuery();
				vt.addData(result,VirtualTableFillMethod.OVERWRITE);
				vt.setLastQuery(lastQuery); // restore query before paging
			}
			finally
			{
				table.setCursor(Cursor.getDefaultCursor());
			}
		}
		
		synchronized(this)
		{
			loadedFirst = requestedFirst;
			loadedLast = requestedLast;
		}
		
		SwingUtilities.invokeLater(new Runnable()
		{
			@Override
			public void run()
			{
				table.saveState();
				
				fireTableDataChanged();
				
				table.restoreState();
			}
		});
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int[] getModelColumnIndices()
	{
		return columnIndices;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getColumnCount()
	{
		return columnIndices.length;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getColumnName(int col)
	{
		return vt.getColumnCaption(columnIndices[col]);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Class getColumnClass(int col)
	{
		return VirtualTableModel.getClassForType(vt.getColumnAt(columnIndices[col]).getType());
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isCellEditable(int row, int col)
	{
		return vt.getColumnAt(columnIndices[col]).isEditable();
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setValueAt(Object newValue, int row, int col)
	{
		try
		{
			row -= loadedFirst;
			
			Object oldValue = getValueAt(row,col);
			int columnIndex = columnIndices[col];
			if(!VirtualTable.equals(oldValue,newValue,vt.getColumnAt(columnIndex).getType()))
			{
				vt.setValueAt(newValue,row,columnIndex);
			}
		}
		catch(Exception e)
		{
			log.error(e);
		}
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public VirtualTable getVirtualTable()
	{
		return vt;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int viewToModelColumn(int col)
	{
		return columnIndices[col];
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public VirtualTableRow getVirtualTableRow(int modelRow)
	{
		return vt.getRow(modelRow - loadedFirst);
	}
	
	
	protected void handle(Throwable e)
	{
		log.error(e);
	}
	
	
	/**
	 * Returns the lazy loading default select.
	 * 
	 * @return the default {@link SELECT}
	 */
	public SELECT getSelect()
	{
		return select;
	}
	
	
	/**
	 * Returns the concerned pager.
	 * 
	 * @return the currently used {@link DBPager}.
	 */
	public DBPager getPager()
	{
		return pager;
	}
	
	
	/**
	 * Returns the currently first requested row.
	 * 
	 * @return the currently first requested row.
	 */
	public int getRequestedFirst()
	{
		return requestedFirst;
	}
	
	
	/**
	 * Returns the currently last requested row.
	 * 
	 * @return the currently last requested row.
	 */
	public int getRequestedLast()
	{
		return requestedLast;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy