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

info.joseluismartin.gui.ListTableModel Maven / Gradle / Ivy

/*
 * Copyright 2008-2011 the original author or authors.
 *
 * 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 info.joseluismartin.gui;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Action;
import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;

/**
 * TableModel that use a List of Objects to hold data.
 * 
 * @author Jose Luis Martin - ([email protected])
 * @see info.joseluismartin.PageableTable
 * @since 1.0
 */
// FIXME: - jlm - This class is growing quicky, revise design.
@SuppressWarnings({"unchecked", "rawtypes"})
public class ListTableModel implements TableModel {
	
	public static final String MAX_WIDTH = "maxWidth";
	public static final String CELL_RENDERER = "cellRenderer";
	private static final String CELL_EDITOR = "cellEditor";
	
	/**  log */
	private final static Log log = LogFactory.getLog(ListTableModel.class);
	
	/** List holder for models */
	
	private List list;
	/** TableModel listeners */
	private ArrayList listeners = new ArrayList();
	/** columnConunt */
	private int columnCount = 0;
	/** Property descriptor array of model */
	private List pds = new ArrayList();
	/** columnNames */
	private List columnNames = new ArrayList();
	/** display names */
	private List displayNames = new ArrayList();
	/** if true, use instrospection to get property and display names */
	private boolean usingIntrospection = false;
	/** true if use checkbox to select or unselect rows */
	private boolean usingChecks = true;
	/** true if use actions at final of rows */
	private boolean usingActions = false;
	/** action list for table rows */
	private List actions = new ArrayList();
	/** List of checks values */
	private List checks = new ArrayList();
	/** Editable Map holds editable state by property name*/
	private Map editableMap = new HashMap();
	/** hold check state by model key */
	private Set selectedRowSet  = new HashSet();
	/** model id property for checkMap */
	private String id = "id";
	/** Model class */
	private Class modelClass;
	/** ColumnDefintion List */
	private List columns = new ArrayList();
	/** Default TableCellRenderer */
	private TableCellRenderer defaultTableCellRenderer;
	
	/**
	 * Creates a new ListTableModel with model set to List l
	 * @param l the list to set as model
	 */
	public ListTableModel(List l) {
		setList(l);
	}

	/**
	 * Creates a new ListTableModel with a empty list model
	 */
	public ListTableModel() {
		setList(new ArrayList());
	}

	/** 
	 * Get the column name of an index
	 * @return String with column name
	 */
	public String getColumnName(int columnIndex) {
		if (isPropertyColumn(columnIndex)) {
			return displayNames.get(columnToPropertyIndex(columnIndex));
		}
		return "";
	}

	/**
	 * {@inheritDoc}
	 */
	public Class getColumnClass(int columnIndex) {
		Class clazz = Object.class;
		
		if (isCheckColum(columnIndex)) {
			clazz = Boolean.class;
		}
		else if (isPropertyColumn(columnIndex)) {
			if (pds.size() > 0) {
				clazz =  pds.get(columnToPropertyIndex(columnIndex)).getPropertyType();
			}
		}
		else if(isActionColumn(columnIndex)) {
			clazz = actions.get(columntoToActionIndex(columnIndex)).getClass();
		}
		
		return clazz;
	}
	
	private int columntoToActionIndex(int columnIndex) {
		return columnIndex - pds.size() - (usingActions ? 1 : 0);
	}

	/**
	 * {@inheritDoc}
	 */
	public int getRowCount() {
		return list.size();
	}

	/**
	 * {@inheritDoc}
	 */
	public int getColumnCount() {
		return columnCount;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return isCheckColum(columnIndex) || isActionColumn(columnIndex) ||
			(isPropertyColumn(columnIndex) && 
				Boolean.TRUE.equals(editableMap.get(getPropertyName(columnIndex))));
	
		//		editable[columnToPropertyIndex(columnIndex)]); 
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Object getValueAt(int rowIndex, int columnIndex) {
		// return check
		if (usingChecks) {
			if (columnIndex == 0) {
				return checks.get(rowIndex); 
			}
			else {
				columnIndex--;
			}
		}
		// return property name
		if (columnIndex < pds.size()) {
			return getCellValue(rowIndex, columnIndex);
		}
		// return Action
		if (usingActions) {
			return actions.get(columnIndex - pds.size());
		}
		
		return null;
	}

	private Object getCellValue(int rowIndex, int columnIndex) {
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(list.get(rowIndex));
		return bw.getPropertyValue(columnNames.get(columnIndex));
	}

	/**
	 * {@inheritDoc}
	 */
	public void setValueAt(Object value, int rowIndex, int columnIndex) {
		if (isCheckColum(columnIndex)) {
			checks.set(rowIndex, (Boolean) value);			
			// sync selectedRowSet
			Object row = list.get(rowIndex);
		
			if (Boolean.TRUE.equals(value))
				selectedRowSet.add((Serializable) getPrimaryKey(row));
			else 
				selectedRowSet.remove(getPrimaryKey(row));
		
			return;
		}
		int index = columnToPropertyIndex(columnIndex);
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(list.get(rowIndex));
		bw.setPropertyValue(columnNames.get(index), value);
		fireTableCellUpdated(rowIndex, columnIndex);
	}
	
	/**
     * Notifies all listeners that the value of the cell at 
     * [row, column] has been updated.
     *
     * @param row  row of cell which has been updated
     * @param column  column of cell which has been updated
     * @see TableModelEvent
     * @see EventListenerList
     */
    public void fireTableCellUpdated(int row, int column) {
        fireTableChanged(new TableModelEvent(this, row, row, column));
    }
    
	/**
	 * {@inheritDoc}
	 */
	public void addTableModelListener(TableModelListener listener) {
		if (!listeners.contains(listener))
			listeners.add(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	public void removeTableModelListener(TableModelListener l) {
		listeners.remove(l);
	}
	
	/** 
	 * Initialize table. Load propertyDescriptors based on columNames or 
	 * model introspection. 
	 */
	// FIXME: PropertyDescriptors  are now unused, review to remove.
	public void init() {
		if (modelClass == null) {
			log.warn("Cannot initilize without  modelClass, set a list of models o specify a model class");
			return;
		}
		
		columnCount = 0;
			
		if (usingIntrospection) {
			pds = Arrays.asList(BeanUtils.getPropertyDescriptors(modelClass));
			Collections.reverse(pds);
			columnNames = new ArrayList(pds.size());
			displayNames = new ArrayList(pds.size());
			
			for (PropertyDescriptor propertyDescriptor : pds) {
					columnNames.add(propertyDescriptor.getName());
					displayNames.add(propertyDescriptor.getDisplayName());
			}
		}
			
		else {
			pds = new ArrayList(columnNames.size());
			for (String name : columnNames) {
				pds.add(BeanUtils.getPropertyDescriptor(modelClass, name));	
			}
		}
		
		columnCount += pds.size();
			
		if (usingChecks) {
			columnCount++;
			buildCheckArray();
		}

		if (usingActions) {
			columnCount += actions.size();
		}
	}

	private void buildCheckArray() {
		checks = new ArrayList(list.size());
		// fill checks list
		for (Object row : list) {
			// for now move from / to selectedObjectSet and old checks to
			// avoid refactor code
			checks.add(selectedRowSet.contains(getPrimaryKey(row)));
		}
	}

	/**
	 * Fire a TableModelChanged event
	 * @param e event to fire
	 */
	public void fireTableChanged(TableModelEvent e) {
		Iterator iter = listeners.iterator();
		while (iter.hasNext())
			((TableModelListener) iter.next()).tableChanged(e);
	}

	/**
	 * Create a TableColumnModel for JTable. 
	 * Try to use sizes and cell renderers from property descriptors.
	 * @return a new TableColumnModel based on PropertyDescriptors
	 */
	public TableColumnModel getTableColumnModel() {
		TableColumnModel tcm = new DefaultTableColumnModel();
		int baseIndex = 0
		;
		if (usingChecks) {
			TableColumn tableColumn = new TableColumn(0);
			tableColumn.setMaxWidth(50);
			tcm.addColumn(tableColumn);
			baseIndex++;
		}
		for (int i = 0; i < columnNames.size(); i++) {
		
			TableColumn tableColumn = new TableColumn(baseIndex + i);
			tableColumn.setHeaderValue(displayNames.get(i));
			
			if (pds != null && pds.size() > 0) {
				PropertyDescriptor descriptor = pds.get(i);
				// property values for TableColums
				if (descriptor != null) {
					Integer maxWidth = getColumnWidth(i);
					if (maxWidth != null)
						tableColumn.setMaxWidth(maxWidth.intValue());
					tableColumn.setCellRenderer(getColumnRenderer(i));
					tableColumn.setCellEditor(getColumnEditor(i));
				}
			}
			
			tcm.addColumn(tableColumn);
		}
		
		if (usingActions) {
			baseIndex += columnNames.size();
			for (int i = 0; i < actions.size(); i++) {
				TableColumn tableColumn = new TableColumn(baseIndex + i);
				tableColumn.setCellRenderer(new ActionCellRenderer());
				tableColumn.setMaxWidth(50);
			//	tableColumn.setCellEditor(new ActionCellEditor())
				tcm.addColumn(tableColumn);
			}
		}
		
		return tcm;
	}
	
	/**
	 * Try to get a TableCellRenderer from PropertyDescriptors or ColumnDefinitions
	 * @param index Column index
	 * @return a TableCellRenderer, null if none configured
	 */
	private TableCellRenderer getColumnRenderer(int index) {
		TableCellRenderer renderer = (TableCellRenderer) pds.get(index).getValue(CELL_RENDERER);
		if (renderer == null && columns.size() > 0)
			renderer = columns.get(index).getRenderer();
		
		if (renderer == null)
			renderer = defaultTableCellRenderer;

		return renderer;
	}
	
	/**
	 * Try to get a TableCellEditor from PropertyDescriptors or ColumnDefinitions
	 * @param index Column index
	 * @return a TableCellEditor, null if none configured
	 */
	private TableCellEditor getColumnEditor(int index) {
		TableCellEditor editor = (TableCellEditor) pds.get(index).getValue(CELL_EDITOR);
		if (editor == null && columns.size() > 0)
			editor = columns.get(index).getEditor();
		
		return editor;
	}
	
	/**
	 * Try to get a column width from PropertyDescriptors or ColumnDefinitions
	 * @param index Column index
	 * @return a column width, null if none configured
	 */
	private Integer getColumnWidth(int index) {
		 Integer maxWidth = (Integer) pds.get(index).getValue(MAX_WIDTH);
		 if (maxWidth == null && columns.size() > 0)
			 maxWidth = columns.get(index).getWidth();
		
		 return maxWidth;
	}

	/**
	 * Add a Object to underlaying list
	 * @param o the object to add
	 * @return true if added
	 */
	public boolean add(Object o) {
		boolean result = list.add(o);
	
		if (usingChecks)
			checks.add(Boolean.FALSE);
		
		if (list.size() == 1) {
			// adding on empty list, need to init
			init();
		}
		fireTableChanged(new TableModelEvent(this, list.size() - 1,
				list.size() - 1, TableModelEvent.ALL_COLUMNS,
				TableModelEvent.INSERT));
		
		return result;
	}

	/** 
	 * Remove a object from underlaying list model
	 * @param index column to remove
	 * @return the removed object
	 */
	public Object remove(int index) {
		Object result = list.remove(index);
		fireTableChanged(new TableModelEvent(this, index, index,
				TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
		return result;
	}
	
	/**
	 * Test if index is a property column
	 * @param column index to check
	 * @return true if index is a property column
	 */
	public boolean isPropertyColumn(int column) {
		if (usingChecks) {
			return column > 0  &&  column <= columnNames.size();
		}
		else {
			return column < columnNames.size();
		}
	}
	
	/**
	 * Test if index is a check column
	 * @param column column index to check
	 * @return true if index is a check column
	 */
	public boolean isCheckColum(int column) {
		return usingChecks && column == 0;
	}
	
	public boolean isActionColumn(int column) {
		return !(isPropertyColumn(column) || isCheckColum(column));
	}
	
	/**
	 * Get a property name from column index, used in PageableTable
	 * @param index to convert
	 * @return converted index
	 */
	public String getPropertyName(int index) {
		if (isPropertyColumn(index)) {
			return columnNames.get(columnToPropertyIndex(index));
		}
		return null;
	}
	
	/**
	 * Return de columnIndex of property
	 * @param propertyName property to find
	 * @return the column index or -1 if not found
	 */
	@SuppressWarnings("unused")
	private int getColumnByPropertyName(String propertyName) {
		int i =  0;
		for (String columnName : columnNames) {
			if (columnName.equals(propertyName)) {
				return isUsingChecks() ? i + 1 : i;
			}
			i++;
		}
		return -1;
	}
	 
	/**
	 * Convert column model index to property index
	 * @param column the column to convert
	 * @return the property index
	 */
	public int columnToPropertyIndex(int column) {
		return usingChecks ? column - 1 : column; 
	}

	/**
	 * Fire a model table changed
	 */
	public void fireTableChanged() {
		fireTableChanged(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
	}
	
	public void setColumnEditable(int columnIndex, boolean value) {
		if (isPropertyColumn(columnIndex))
			editableMap.put(getPropertyName(columnIndex), value);
	}
	
	public void setEditableMap(Map editableMap) {
		this.editableMap = editableMap;
	}
	
	

	// Getters and Setters
	
	public void setList(List list) {
		this.list = list;
		// initialize if not already initialized or class model changes
		boolean initilized = pds.size() > 0;
		boolean modelClassChanged = list.size() > 0 && 
			!list.get(0).getClass().equals(modelClass);

		if (!initilized || modelClassChanged) {
			if (modelClassChanged)
				modelClass = list.get(0).getClass();
			init();
		}
		
		if (usingActions)
			buildCheckArray();

		fireTableChanged();
		
	}

	public List getList() {
		return list;
	}

	
	public Iterator iterator() {
		return list.iterator();
	}

	
	public boolean isUsingIntrospection() {
		return usingIntrospection;
	}

	public void setUsingIntrospection(boolean usingIntrospection) {
		this.usingIntrospection = usingIntrospection;
	}

	public List getColumnNames() {
		return columnNames;
	}
	
	public int getPropertyCount() {
		return columnNames.size();
	}
	
	public void setColumnNames(List columnNames) {
		this.columnNames = columnNames;
	}

	public List getDisplayNames() {
		return displayNames;
	}

	public void setDisplayNames(List displayNames) {
		this.displayNames = displayNames;
	}

	public boolean isUsingChecks() {
		return usingChecks;
	}

	public void setUsingChecks(boolean useChecks) {
		this.usingChecks = useChecks;
	}

	public List getActions() {
		return actions;
	}

	public void setActions(List actions) {
		this.actions = actions;
	}

	public boolean isUsingActions() {
		return usingActions;
	}

	public void setUsingActions(boolean useActions) {
		this.usingActions = useActions;
	}

	public Map getEditableMap() {
		return editableMap;
	}
	
	/**
	 * Get a primary key of entity in the list
	 * @param row row of model
	 * @return the primary key of model, if any
	 */
	private Object getPrimaryKey(Object row) {
		BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(row);
		return wrapper.getPropertyValue(id);
	}
	
	/**
	 * Check a list of keys
	 * @param keys
	 */
	public void check(List keys) { 
		if (usingChecks) {
			selectedRowSet.addAll(keys);
			fillChecks(true);
		}
	}
	
	/**
	 * Get a List with all selected model keys
	 * @return
	 */
	public List getChecked() {
		return new ArrayList(selectedRowSet);
	}
	
	/**
	 * Uncheck All checks
	 */
	public void uncheckAll() {
		if (usingChecks) {
			selectedRowSet.clear();
			fillChecks(false);
		}
	}

	/** 
	 * Sets all checks to value 
	 * @param value
	 */
	private void fillChecks(boolean value) {
		for (int i = 0; i < checks.size(); i++) 
			checks.set(i, value);
		
		fireTableChanged(new TableModelEvent(this, 0, list.size() - 1));
	}
	
	/**
	 * Add an action to action List
	 * @param action
	 */
	public void addAction(Action action) {
		if (!actions.contains(action))
			actions.add(action);
	}

	/**
	 * @return the modelClass
	 */
	public Class getModelClass() {
		return modelClass;
	}

	/**
	 * @param modelClass the modelClass to set
	 */
	public void setModelClass(Class modelClass) {
		this.modelClass = modelClass;
	}

	/**
	 * @return the columns
	 */
	public List getColumns() {
		return columns;
	}

	/**
	 * @param columns the columns to set
	 */
	public void setColumns(List columns) {
		this.columns = columns;
		parseColumnDefinitions();
	}
	
	/**
	 * Parse columns definitions to internal state
	 */
	private void parseColumnDefinitions() {
		displayNames.clear();
		columnNames.clear();
		editableMap.clear();
		
		for (ColumnDefinition cd : columns) {
			columnNames.add(cd.getName());
			displayNames.add(cd.getDisplayName());
			editableMap.put(cd.getName(), cd.isEditable());
		}
	}

	/**
	 * @return the defaultTableCellRenderer
	 */
	public TableCellRenderer getDefaultTableCellRenderer() {
		return defaultTableCellRenderer;
	}

	/**
	 * @param defaultTableCellRenderer the defaultTableCellRenderer to set
	 */
	public void setDefaultTableCellRenderer(
			TableCellRenderer defaultTableCellRenderer) {
		this.defaultTableCellRenderer = defaultTableCellRenderer;
	}

	/**
	 * @param column
	 * @return
	 */
	public String getSortPropertyName(int column) {
		String sortPropertyName = null;
		if (isPropertyColumn(column)) {
			if (columns.size() > column)
				sortPropertyName = columns.get(columnToPropertyIndex(column)).getSortProperty();
				if (sortPropertyName == null)
					sortPropertyName = getPropertyName(column);
		}
		
		return sortPropertyName;
	}
}