package nl.tudelft.simulation.dsol.swing.introspection.gui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.table.AbstractTableModel;
import org.djutils.immutablecollections.ImmutableMap;
import org.djutils.logger.CategoryLogger;
import nl.tudelft.simulation.introspection.AbstractProperty;
import nl.tudelft.simulation.introspection.Introspector;
import nl.tudelft.simulation.introspection.Property;
import nl.tudelft.simulation.introspection.beans.BeanIntrospector;
/**
* A tablemodel used to manage and present the instances of a map property.
*
* Copyright (c) 2018-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information https://simulation.tudelft.nl. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
*
* https://simulation.tudelft.nl/dsol/3.0/license.html.
*
* @author Alexander Verbraeck.
*/
public class MapTableModel extends AbstractTableModel implements IntrospectingTableModelInterface
{
/** */
private static final long serialVersionUID = 20140831L;
/** the keys of the map entries. */
protected Map keyMap = Collections.synchronizedMap(new LinkedHashMap(20));
/** the values of the map entries. */
protected Map valueMap = Collections.synchronizedMap(new LinkedHashMap(20));
/** the keys identifying specific instances. */
protected List rowKeys = Collections.synchronizedList(new ArrayList(20));
/** the COLUMNS of this tabbleModel. */
private static final String[] COLUMNS = {"#", "+", "Key", "Value"};
/** the expand button. */
private List buttons = Collections.synchronizedList(new ArrayList(20));
/** the parentProperty */
private Property parentProperty;
/** the introspector. */
private Introspector introspector;
/** The model manager. */
private ModelManager manager = new DefaultModelManager();
/** The highest key currently allocated. */
private int maxKey = 0;
/**
* constructs a new CollectionTableModel.
* @param parentProperty Property; the parentPropert
*/
public MapTableModel(final Property parentProperty)
{
this(parentProperty, new BeanIntrospector());
}
/**
* constructs a new CollectionTableModel.
* @param parentProperty Property; the parentProperty
* @param introspector Introspector; the introspector to use
*/
public MapTableModel(final Property parentProperty, final Introspector introspector)
{
Object values;
try
{
values = parentProperty.getValue();
}
catch (Exception e)
{
values = new LinkedHashMap<>();
}
if (values instanceof Map)
{
Map, ?> map = (Map, ?>) values;
for (Object key : map.keySet())
{
addValue(key, map.get(key));
}
}
if (values instanceof ImmutableMap)
{
ImmutableMap, ?> map = (ImmutableMap, ?>) values;
for (Object key : map.keySet())
{
addValue(key, map.get(key));
}
}
this.parentProperty = parentProperty;
this.introspector = introspector;
// Initialize buttons
for (int i = 0; i < this.keyMap.size(); i++)
{
ExpandButton button = new ExpandButton(getProperty(i), this);
this.buttons.add(button);
}
this.fireTableDataChanged();
}
/**
* Adds a new value to the managed composite property.
* @param key Object; the key
* @param value Object; the value to add
*/
private void addValue(final Object key, final Object value)
{
Integer nextKey = Integer.valueOf(this.maxKey++);
this.rowKeys.add(nextKey);
this.keyMap.put(nextKey, key);
this.valueMap.put(nextKey, value);
}
/** {@inheritDoc} */
@Override
public int getRowCount()
{
return this.keyMap.size();
}
/** {@inheritDoc} */
@Override
public int getColumnCount()
{
return MapTableModel.COLUMNS.length;
}
/** {@inheritDoc} */
@Override
public Object getValueAt(final int rowIndex, final int columnIndex)
{
if (columnIndex == 0)
{
return Integer.valueOf(rowIndex);
}
if (columnIndex == 1)
{
return this.buttons.get(rowIndex);
}
if (columnIndex == 2)
{
return this.keyMap.get(this.rowKeys.get(rowIndex));
}
if (columnIndex == 3)
{
return this.valueMap.get(this.rowKeys.get(rowIndex));
}
return null;
}
/** {@inheritDoc} */
@Override
public String getColumnName(final int columnIndex)
{
return MapTableModel.COLUMNS[columnIndex];
}
/** {@inheritDoc} */
@Override
public boolean isCellEditable(final int rowIndex, final int columnIndex)
{
// NOTE: For a button to be clickable, it needs to be editable!!!
if (columnIndex == 1)
{
return true;
}
return false;
}
/** {@inheritDoc} */
@Override
public void setValueAt(final Object keyValue, final int rowIndex, final int columnIndex)
{
if (columnIndex == 1)
return; // Clicked a button...
CategoryLogger.always().warn("cannot set values in a Map...");
throw new IllegalArgumentException("cannot set values in a Map...");
}
/** {@inheritDoc} */
@Override
public Class> getColumnClass(final int columnIndex)
{
if (columnIndex == 1)
{
return ExpandButton.class;
}
return Object.class;
}
/**
* The collection table model labels all properties according to their rowIndex. Only these labels are expected to be
* requested here.
* @see nl.tudelft.simulation.dsol.swing.introspection.gui.IntrospectingTableModelInterface #getProperty(java.lang.String)
*/
@Override
public Property getProperty(final String propertyName)
{
int index = Integer.parseInt(propertyName);
return getProperty(index);
}
/**
* @param index int; the index of the property
* @return the Property
*/
protected Property getProperty(final int index)
{
return new MapProperty(this.rowKeys.get(index), this.parentProperty.getName());
}
/** {@inheritDoc} */
@Override
public Introspector getIntrospector()
{
return this.introspector;
}
/** {@inheritDoc} */
@Override
public Class> getTypeAt(final int rowIndex, final int columnIndex)
{
if (columnIndex == 0)
{
return String.class;
}
if (columnIndex == 1)
{
return ExpandButton.class;
}
if (columnIndex == 2)
{
return this.keyMap.get(this.rowKeys.get(rowIndex)).getClass();
}
if (columnIndex == 3)
{
return this.valueMap.get(this.rowKeys.get(rowIndex)).getClass();
}
return null;
}
/**
* Sets the modelmanager. By default, a {see DefaultModelManager}is used.
* @param manager ModelManager; the manager
*/
public void setModelManager(final ModelManager manager)
{
this.manager = manager;
}
/**
* By default, a {see DefaultModelManager}returned.
* @see nl.tudelft.simulation.dsol.swing.introspection.gui.IntrospectingTableModelInterface #getModelManager()
* @return the Manager
*/
@Override
public ModelManager getModelManager()
{
return this.manager;
}
/**
* The MapProperty.
*/
class MapProperty extends AbstractProperty implements Property
{
/** the key of this property. */
private final Integer key;
/** the name. */
private final String name;
/**
* This implementation is NOT thread-safe. When multiple users will edit the parent at the same time, errors are
* expected.
* @param key Integer; the key
* @param name String; the name
*/
MapProperty(final Integer key, final String name)
{
this.key = key;
this.name = name;
}
/** {@inheritDoc} */
@Override
public Object getInstance()
{
return MapTableModel.this.valueMap.values();
}
/** {@inheritDoc} */
@Override
public String getName()
{
return this.name + "[" + MapTableModel.this.rowKeys.indexOf(this.key) + "]";
}
/** {@inheritDoc} */
@Override
public Class> getType()
{
return MapTableModel.this.valueMap.get(this.key).getClass();
}
/** {@inheritDoc} */
@Override
public Object getValue()
{
try
{
return MapTableModel.this.valueMap.get(this.key);
}
catch (Exception e)
{
return new String("-");
}
}
/** {@inheritDoc} */
@Override
public boolean isEditable()
{
return false;
}
/** {@inheritDoc} */
@Override
protected void setRegularValue(final Object value)
{
throw new IllegalArgumentException(this + " is only supposed to be" + " set to composite values."
+ "A program is not supposed to arrive here.");
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "Map.Prop, key:" + this.key;
}
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "MapTableModel";
}
}