View Javadoc
1   package nl.tudelft.simulation.dsol.swing.introspection.gui;
2   
3   import java.lang.reflect.Array;
4   import java.lang.reflect.Constructor;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.Iterator;
10  import java.util.LinkedHashMap;
11  import java.util.List;
12  import java.util.Map;
13  
14  import javax.swing.table.AbstractTableModel;
15  
16  import org.djutils.immutablecollections.ImmutableCollection;
17  import org.djutils.logger.CategoryLogger;
18  
19  import nl.tudelft.simulation.dsol.swing.introspection.table.DynamicTableModel;
20  import nl.tudelft.simulation.introspection.AbstractProperty;
21  import nl.tudelft.simulation.introspection.Introspector;
22  import nl.tudelft.simulation.introspection.Property;
23  import nl.tudelft.simulation.introspection.beans.BeanIntrospector;
24  
25  /**
26   * A tablemodel used to manage and present the instances of a composite property.
27   * <p>
28   * Copyright (c) 2002-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
29   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
30   * project is distributed under a three-clause BSD-style license, which can be found at
31   * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
32   * https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
33   * </p>
34   * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs</a>.
35   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>.
36   * @author Niels Lang.
37   * @since 1.5
38   */
39  public class CollectionTableModel extends AbstractTableModel implements IntrospectingTableModelInterface, DynamicTableModel
40  {
41      /** */
42      private static final long serialVersionUID = 20140831L;
43  
44      /** the instances of the collection. */
45      protected Map<Integer, Object> instances = Collections.synchronizedMap(new LinkedHashMap<Integer, Object>(20));
46  
47      /** the keys identifying specific instances. */
48      protected List<Integer> keys = Collections.synchronizedList(new ArrayList<Integer>(20));
49  
50      /** the componentType. */
51      private Class<?> componentType = null;
52  
53      /** the COLUMNS of this tabbleModel. */
54      private static final String[] COLUMNS = {"#", "+", "Instance"};
55  
56      /** the expand button. */
57      private List<ExpandButton> buttons = Collections.synchronizedList(new ArrayList<ExpandButton>(20));
58  
59      /** the parentProperty */
60      private Property parentProperty;
61  
62      /** the introspector. */
63      private Introspector introspector;
64  
65      /** The model manager. */
66      private ModelManager manager = new DefaultModelManager();
67  
68      /** The highest key currently allocated. */
69      private int maxKey = 0;
70  
71      /**
72       * constructs a new CollectionTableModel.
73       * @param parentProperty Property; the parentPropert
74       */
75      public CollectionTableModel(final Property parentProperty)
76      {
77          this(parentProperty, new BeanIntrospector());
78      }
79  
80      /**
81       * constructs a new CollectionTableModel.
82       * @param parentProperty Property; the parentProperty
83       * @param introspector Introspector; the introspector to use
84       */
85      public CollectionTableModel(final Property parentProperty, final Introspector introspector)
86      {
87          Object values;
88          try
89          {
90              values = parentProperty.getValue();
91          }
92          catch (Exception e)
93          {
94              values = new String("-");
95          }
96          if (values.getClass().isArray())
97          {
98              for (int i = 0; i < Array.getLength(values); i++)
99              {
100                 addValue(Array.get(values, i));
101             }
102         }
103         if (values instanceof Collection)
104         {
105             for (Iterator<?> i = ((Collection<?>) values).iterator(); i.hasNext();)
106             {
107                 addValue(i.next());
108             }
109         }
110         if (values instanceof ImmutableCollection)
111         {
112             for (Iterator<?> i = ((ImmutableCollection<?>) values).iterator(); i.hasNext();)
113             {
114                 addValue(i.next());
115             }
116         }
117         this.parentProperty = parentProperty;
118         this.introspector = introspector;
119         // Initialize buttons
120         for (int i = 0; i < this.instances.size(); i++)
121         {
122             this.buttons.add(new ExpandButton(getProperty(i), this));
123         }
124     }
125 
126     /**
127      * Adds a new value to the managed composite property.
128      * @param value Object; the value to add
129      */
130     private void addValue(final Object value)
131     {
132         Integer nextKey = Integer.valueOf(this.maxKey++);
133         this.keys.add(nextKey);
134         this.instances.put(nextKey, value);
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public int getRowCount()
140     {
141         return this.instances.size();
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     public int getColumnCount()
147     {
148         return CollectionTableModel.COLUMNS.length;
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public Object getValueAt(final int rowIndex, final int columnIndex)
154     {
155         if (columnIndex == 0)
156         {
157             return Integer.valueOf(rowIndex);
158         }
159         if (columnIndex == 1)
160         {
161             return this.buttons.get(rowIndex);
162         }
163         if (columnIndex == 2)
164         {
165             return this.instances.get(this.keys.get(rowIndex));
166         }
167         return null;
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public String getColumnName(final int columnIndex)
173     {
174         return CollectionTableModel.COLUMNS[columnIndex];
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public boolean isCellEditable(final int rowIndex, final int columnIndex)
180     {
181         if (columnIndex == 1 || columnIndex == 2)
182         {
183             return true;
184         }
185         return false;
186     }
187 
188     /** {@inheritDoc} */
189     @Override
190     public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex)
191     {
192         if (columnIndex == 2)
193         {
194             Integer key = this.keys.get(rowIndex);
195             this.instances.put(key, aValue);
196         }
197         this.update();
198     }
199 
200     /**
201      * updates the tableModel
202      */
203     private void update()
204     {
205         // Generate a List reflecting changes
206         List<Object> newValue = new ArrayList<Object>(this.keys.size());
207         for (int i = 0; i < this.keys.size(); i++)
208         {
209             newValue.add(this.instances.get(this.keys.get(i)));
210         }
211         this.parentProperty.setValue(newValue);
212         this.fireTableDataChanged();
213     }
214 
215     /** {@inheritDoc} */
216     @Override
217     public Class<?> getColumnClass(final int columnIndex)
218     {
219         if (columnIndex == 1)
220         {
221             return ExpandButton.class;
222         }
223         return Object.class;
224     }
225 
226     /**
227      * The collection table model labels all properties according to their rowIndex. Only these labels are expected to be
228      * requested here.
229      * @see nl.tudelft.simulation.dsol.swing.introspection.gui.IntrospectingTableModelInterface #getProperty(java.lang.String)
230      */
231     @Override
232     public Property getProperty(final String propertyName)
233     {
234         int index = Integer.parseInt(propertyName);
235         return getProperty(index);
236     }
237 
238     /**
239      * @param index int; the index of the property
240      * @return the Property
241      */
242     protected Property getProperty(final int index)
243     {
244         return new CollectionProperty(this.keys.get(index), this.parentProperty.getName());
245     }
246 
247     /** {@inheritDoc} */
248     @Override
249     public void createRow()
250     {
251         if (this.componentType == null)
252         {
253             this.componentType = this.parentProperty.getComponentType();
254             if (this.componentType == null)
255             {
256                 return;
257             }
258         }
259         try
260         {
261             Constructor<?> instanceConstructor = this.componentType.getConstructor(new Class[0]);
262             Object instance = instanceConstructor.newInstance(new Object[0]);
263             addValue(instance);
264             this.buttons.add(new ExpandButton(getProperty(this.instances.size() - 1), this));
265             update();
266         }
267         catch (Exception e)
268         {
269             CategoryLogger.always().warn(e, "createRow: Could not instantiate new instance: ");
270         }
271     }
272 
273     /** {@inheritDoc} */
274     @Override
275     public void createRows(final int amount)
276     {
277         for (int i = 0; i < amount; i++)
278         {
279             this.createRow();
280         }
281     }
282 
283     /** {@inheritDoc} */
284     @Override
285     public void deleteRow(final int index)
286     {
287         Integer deletionKey = this.keys.get(index);
288         this.instances.remove(deletionKey);
289         this.keys.remove(index);
290         this.buttons.remove(index);
291         update();
292     }
293 
294     /** {@inheritDoc} */
295     @Override
296     public synchronized void deleteRows(final int[] indices)
297     {
298         Arrays.sort(indices);
299         for (int i = indices.length - 1; i >= 0; i--)
300         {
301             deleteRow(indices[i]);
302         }
303     }
304 
305     /** {@inheritDoc} */
306     @Override
307     public Introspector getIntrospector()
308     {
309         return this.introspector;
310     }
311 
312     /** {@inheritDoc} */
313     @Override
314     public Class<?> getTypeAt(final int rowIndex, final int columnIndex)
315     {
316         if (columnIndex == 0)
317         {
318             return String.class;
319         }
320         if (columnIndex == 1)
321         {
322             return ExpandButton.class;
323         }
324         if (columnIndex == 2)
325         {
326             return this.instances.get(this.keys.get(rowIndex)).getClass();
327         }
328         return null;
329     }
330 
331     /**
332      * Sets the modelmanager. By default, a {see DefaultModelManager}is used.
333      * @param manager ModelManager; the manager
334      */
335     public void setModelManager(final ModelManager manager)
336     {
337         this.manager = manager;
338     }
339 
340     /**
341      * By default, a {see DefaultModelManager}returned.
342      * @see nl.tudelft.simulation.dsol.swing.introspection.gui.IntrospectingTableModelInterface #getModelManager()
343      * @return the Manager
344      */
345     @Override
346     public ModelManager getModelManager()
347     {
348         return this.manager;
349     }
350 
351     /** {@inheritDoc} */
352     @Override
353     public boolean isRowEditable()
354     {
355         return this.parentProperty.isEditable();
356     }
357 
358     /**
359      * The CollectionProperty.
360      */
361     class CollectionProperty extends AbstractProperty implements Property
362     {
363         /** the key of this property. */
364         private final Integer key;
365 
366         /** the name. */
367         private final String name;
368 
369         /**
370          * This implementation is NOT thread-safe. When multiple users will edit the parent at the same time, errors are
371          * expected.
372          * @param key Integer; the key
373          * @param name String; the name
374          */
375         CollectionProperty(final Integer key, final String name)
376         {
377             this.key = key;
378             this.name = name;
379         }
380 
381         /** {@inheritDoc} */
382         @Override
383         public Object getInstance()
384         {
385             return CollectionTableModel.this.instances.values();
386         }
387 
388         /** {@inheritDoc} */
389         @Override
390         public String getName()
391         {
392             return this.name + "[" + CollectionTableModel.this.keys.indexOf(this.key) + "]";
393         }
394 
395         /** {@inheritDoc} */
396         @Override
397         public Class<?> getType()
398         {
399             return CollectionTableModel.this.instances.get(this.key).getClass();
400         }
401 
402         /** {@inheritDoc} */
403         @Override
404         public Object getValue()
405         {
406             try
407             {
408                 return CollectionTableModel.this.instances.get(this.key);
409             }
410             catch (Exception e)
411             {
412                 return new String("-");
413             }
414         }
415 
416         /** {@inheritDoc} */
417         @Override
418         public boolean isEditable()
419         {
420             return true;
421         }
422 
423         /** {@inheritDoc} */
424         @Override
425         protected void setRegularValue(final Object value)
426         {
427             throw new IllegalArgumentException(this + " is only supposed to be" + " set to composite values."
428                     + "A program is not supposed to arrive here.");
429         }
430 
431         /** {@inheritDoc} */
432         @Override
433         public String toString()
434         {
435             return "Coll.Prop, key:" + this.key;
436         }
437     }
438 
439     /** {@inheritDoc} */
440     @Override
441     public String toString()
442     {
443         return "CollectionTableModel";
444     }
445 
446 }