1// BEGIN LICENSE BLOCK
2// Version: CMPL 1.1
3//
4// The contents of this file are subject to the Cisco-style Mozilla Public
5// License Version 1.1 (the "License"); you may not use this file except
6// in compliance with the License.  You may obtain a copy of the License
7// at www.eclipse-clp.org/license.
8//
9// Software distributed under the License is distributed on an "AS IS"
10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
11// the License for the specific language governing rights and limitations
12// under the License.
13//
14// The Original Code is  The ECLiPSe Constraint Logic Programming System.
15// The Initial Developer of the Original Code is  Cisco Systems, Inc.
16// Portions created by the Initial Developer are
17// Copyright (C) 2006 Cisco Systems, Inc.  All Rights Reserved.
18//
19// Contributor(s):
20//
21// END LICENSE BLOCK
22
23package com.parctechnologies.eclipse.visualisation;
24
25import java.util.*;
26import java.beans.*;
27import javax.swing.table.*;
28import javax.swing.*;
29import com.parctechnologies.eclipse.*;
30
31/**
32 * Basic implementation of the ViewletDataStore interface
33 */
34public abstract class AbstractViewletDataStore
35    extends AbstractTableModel
36    implements ViewletDataStore
37{
38  protected static final String fixedString = "fixed";
39  protected static final String flexibleString = "flexible";
40
41  // holds symref for this store
42  SymRef symRef;
43
44
45  // Handle to the viewable for which this is a store
46  protected Viewable viewable;
47
48  // used to store the size expanded *from* in between a startExpandDimension
49  // and a finishExpandDimension.
50  protected List oldSize;
51
52  /**
53   * Array of Lists of Strings. Each element in the array relates to a
54   * dimension. Each List is a list of strings: location names for the
55   * corresponding dimension. <p>
56   * locationNames[0] is a List of location names for dimension 1 in the
57   * viewable,
58   * locationNames[1] is for dimension 2 etc.
59   */
60  protected List[] locationNames;
61
62  /**
63   * Atom which states whether the top level dimension is fixed or flexible.
64   */
65  protected String topLevelFixity;
66
67  /**
68   * This is a list of Integers which lists the size of Elements recursively:
69   * i.e. if Elements contained 7 2-d ViewletArrays, each one with dimensions
70   * 4 x 8 then wholeSize = [7,4,8].
71   */
72  protected List wholeSize;
73
74  /**
75   * viewletFactory is used to create the new Viewlets which will populate the
76   * ViewletArray.
77   */
78  protected ViewletFactory viewletFactory;
79
80
81  /**
82   * Holds the list of all listeners
83   */
84  protected List listenerList;
85
86  /**
87   * Construct a ViewletArray given a list of integers (size), a fixity list
88   * (fixity) and a ViewletFactory to be used to populate it with Viewlets.
89   *
90   */
91  public AbstractViewletDataStore(List size,
92				  List fixity,
93                                  Viewable viewable,
94				  ViewletFactory viewletFactory)
95  {
96    int topLevelSize = ((Integer) size.get(0)).intValue();
97    this.wholeSize = new LinkedList(size);
98    this.topLevelFixity = (String) fixity.get(0);
99    this.viewable = viewable;
100    this.viewletFactory = viewletFactory;
101    this.listenerList = new LinkedList();
102    locationNames = new List[getNDimensions()];
103  }
104
105  /**
106   * dimNumber starts from 1. List should be a list of Strings.
107   */
108  public void setLocationNames(int dimNumber, List locationNames)
109  {
110    this.locationNames[dimNumber - 1] = locationNames;
111
112    if (DebuggingSupport.logMessages) {
113	DebuggingSupport.logMessage(this, "Location names of dimension "+
114				    dimNumber+" set to "+locationNames);
115    }
116
117  }
118
119    /**
120     * dimNumber starts from 1.  Fire propertyChange events to
121     * indicate that the store has changed shape */
122    public void finishExpandDimension(int dimNumber)
123    {
124
125	if (DebuggingSupport.logMessages) {
126	    DebuggingSupport.logMessage(this, "notifying property change listeners of expand");
127	}
128
129	//propertyChangeSupport.firePropertyChange("arraySize", oldSize, wholeSize);
130	switch( dimNumber ) {
131	case 1 : {
132	    int oldLimit = ((Integer)oldSize.get(0)).intValue();
133	    int newLimit = ((Integer)wholeSize.get(0)).intValue();
134	    fireTableRowsInserted(oldLimit,newLimit-1);
135	    break;
136	}
137	case 2: {
138	    // A new column has been added
139	    //int oldLimit = ((Integer)oldSize.get(1)).intValue();
140	    //int newLimit = ((Integer)whole.get(1)).intValue();
141	    fireTableStructureChanged();
142	    //fireTableColumnsInserted(oldLimit,newLimit-1);
143	    break;
144	}
145	default: {
146	    //do nothing
147	    //fireTableStructureChanged();
148	}
149	}
150    }
151
152  /**
153   * dimNumber starts from 1.
154   */
155  public List getLocationNames(int dimNumber)
156  {
157    return(locationNames[dimNumber - 1]);
158  }
159
160  /**
161   * dimNumber & locNumber start from 1.
162   */
163  public String getLocationName(int dimNumber, int locNumber)
164  {
165    return((String) getLocationNames(dimNumber).get(locNumber - 1));
166  }
167
168  /**
169   * Set the Java handle to the ECLiPSe side viewable
170   */
171  public void setViewable(Viewable viewable) {
172    this.viewable = viewable;
173  }
174
175  /**
176   * Get the Java handle to the ECLiPSe side viewable
177   */
178  public Viewable getViewable() {
179    return viewable;
180  }
181
182  /**
183   * Traverses the entire structure recursively to collect together all the
184   * viewlet indices.
185   */
186    public ViewletRange getEntireViewletRange() {
187        // create an index representing the first element in the viewable
188        List start = new ArrayList(wholeSize.size());
189        Integer one = new Integer(1);
190        for(int i = 0; i < wholeSize.size(); i++) {
191            start.add(one);
192        }
193        //    createdElementIndices = allCombinations(size);
194        return createRange(start,wholeSize);
195    }
196
197
198    /**
199     * Returns a data iterator for the given ViewletRange.  While the
200     * specified ViewletRange.iterator() will return indices, this
201     * Iterator will return copies of the ViewletData in the store.  */
202    public Iterator getViewletDataIterator(ViewletRange range) {
203	return new ViewletDataIterator(this, range);
204    }
205
206
207    protected static class ViewletDataIterator implements Iterator {
208	Iterator it; // store the underlying range iterator
209	ViewletDataStore store ;
210	public ViewletDataIterator(ViewletDataStore store,
211				   ViewletRange range) {
212	    this.it = range.iterator();
213	    this.store = store;
214	}
215	public boolean hasNext() {
216	    return it.hasNext();
217	}
218	public Object next() {
219	    List index = (List)(it.next());
220            if (DebuggingSupport.logMessages) {
221                DebuggingSupport.logMessage(this, "DataIterator index="+index);
222            }
223	    return store.getViewletDataAt(index);
224	}
225	public void remove() {
226	    it.remove();
227	}
228    };
229
230
231
232  public int getNDimensions()
233  {
234    return(getSize().size());
235  }
236
237  public List getSize()
238  {
239    return(wholeSize);
240  }
241
242  /**
243   * Shrinks the ViewletArray to size newSize. Assumes the top-level fixity is
244   * flexible.
245   */
246  public void shrinkTo(List newSize)
247  {
248
249      if (DebuggingSupport.logMessages) {
250	  DebuggingSupport.logMessage(this, "shrinking to "+newSize);
251      }
252
253
254      if (DebuggingSupport.logMessages) {
255	  DebuggingSupport.logMessage(this, "notifying property change listeners of shrink");
256      }
257  }
258
259  /**
260   * Expand dimension nested at level <code>dimension</code> by one.
261   * records the pre-expansion size in the variable oldSize.
262   */
263    public void startExpandDimension(int dimension)
264  {
265    Integer oldDimSize = (Integer) wholeSize.get(dimension-1);
266    Integer newDimSize =
267      new Integer(oldDimSize.intValue() + 1);
268    oldSize = new LinkedList(wholeSize);
269    wholeSize.set(dimension-1, newDimSize);
270
271    if (DebuggingSupport.logMessages) {
272	DebuggingSupport.logMessage(this,
273				    "Viewlet array expanding to size:"+
274				    wholeSize);
275    }
276  }
277
278  /**
279   * Handy method to get the Viewlet at co-ordinates (row, column) in the case
280   * of a 2-d ViewletArray
281   */
282  public ViewletData getViewletDataAt(int row, int column)
283  {
284    if(getSize().size() != 2)
285    {
286      return(null);
287    }
288    LinkedList index = new LinkedList();
289    index.add(new Integer(row));
290    index.add(new Integer(column));
291    return(getViewletDataAt(index));
292  }
293
294  /**
295   * Handy method to get the Viewlet at co-ordinate row in the case
296   * of a 1-d ViewletArray
297   */
298  public ViewletData getViewletDataAt(int row)
299  {
300    if(getSize().size() != 1)
301    {
302      return(null);
303    }
304    LinkedList index = new LinkedList();
305    index.add(new Integer(row));
306    return(getViewletDataAt(index));
307  }
308
309    /**
310     * This method must be overridden in sub-classes
311     */
312    public abstract ViewletData getViewletDataAt(List index);
313
314    /**
315     * This method must be overridden in sub-classes
316     */
317    public abstract void setViewletDataAt(List index, ViewletData data);
318
319
320  /**
321   * Fire data changed events upto any listeneing GUIs.
322   *
323   * <p>Default implemenation only fires table change events for 1 or
324   * two dimension viewables. */
325  public void fireViewletRangeUpdated(ViewletRange range) {
326    SwingUtilities.invokeLater(new RangeUpdater(this,range));
327    //new RangeUpdater(this,range).run();
328  }
329
330  /**
331   * Adds a ViewletDataStoreListener
332   */
333  public synchronized void addViewletDataStoreListener(ViewletDataStoreListener listener) {
334    listenerList.add(listener);
335  }
336
337  /**
338   * Removes a ViewletDataStoreListener
339   */
340  public synchronized void removeViewletDataStoreListener(ViewletDataStoreListener listener) {
341    listenerList.remove(listener);
342  }
343
344
345
346  //-----------BEGIN: Implement the TableModel methods------------------
347
348  /**
349   * Return number of rows
350   */
351  public int getRowCount() {
352    int row =  ((Integer)wholeSize.get(0)).intValue();
353    return row;
354  }
355  /**
356   * Return number of columns
357   */
358  public int getColumnCount() {
359    int col = 1;
360    if (wholeSize.size() > 1) {
361      col =  ((Integer)wholeSize.get(1)).intValue();
362    }
363    return col;
364  }
365
366  /**
367   * Return the viewlet at the given location.
368   * Note that the TableModel counts from 0, where as the Viewlet
369   * locations start from 1.
370   */
371  public Object getValueAt(int row, int col) {
372    if (wholeSize.size() == 1) {
373      return getViewletDataAt(row+1);
374    }
375    return getViewletDataAt(row+1, col+1);
376  }
377
378  /**
379   * Returns the column name for the given column
380   */
381  public String getColumnName(int column) {
382    if (wholeSize.size() == 1) {
383      return "1";
384    }
385    return (String)(locationNames[1].get(column));
386  }
387
388  /**
389   * Returns the column name for the given column
390   */
391  public String getRowName(int row) {
392    return (String)(locationNames[0].get(row));
393  }
394
395  //public Class getColumnClass(int c) {return getValueAt(0, c).getClass();}
396  public boolean isCellEditable(int row, int col) {
397    return false;
398  }
399
400  //-----------ENDOF: Implement the TableModel methods------------------
401
402  /**
403   * Factory method for creating ViewletRange object from any given
404   * collection.
405   *
406   * <p>This defualt implementation will create a generic
407   * ViewletRange.  Subclasses can override this if specialised
408   * structures make more sense.  */
409  public ViewletRange createRange(Collection indices) {
410    return new ViewletRangeCollection(indices);
411  }
412
413
414  /**
415   * Given a fromList and toList of integers returns a list of all
416   * lists X where X[i] is between fromList[i] and toList[i],
417   * inclusive.
418   *
419   * (e.g. for fromList = [1,1,1] toList = [3,2,2]) the method would
420   * return
421   *
422   * [1,1,1], [1,1,2], [1,2,1], [1,2,2], [2,1,1], ..., [3,2,1],
423   * [3,2,2].  */
424  protected List allCombinations(List fromIndex, List toIndex) {
425    List result = new LinkedList();
426    if(toIndex.size() > 0)
427    {
428      Integer fromHead = (Integer) fromIndex.get(0);
429      List fromTail = fromIndex.subList(1, fromIndex.size());
430      Integer toHead = (Integer) toIndex.get(0);
431      List toTail = toIndex.subList(1, toIndex.size());
432      List subCombs = allCombinations(fromTail, toTail);
433      Iterator subCombsIterator;
434      List currentComb;
435      for(int i = fromHead.intValue(); i <= toHead.intValue(); i++)
436      {
437        subCombsIterator = subCombs.iterator();
438        while(subCombsIterator.hasNext())
439        {
440          currentComb = new LinkedList();
441          currentComb.add(new Integer(i));
442          currentComb.addAll((List) subCombsIterator.next());
443          result.add(currentComb);
444        }
445      }
446    }
447    else
448    {
449      result.add(new LinkedList());
450    }
451    return(result);
452  }
453
454  /**
455   * Factory method for creating ViewletRange objects to cover all
456   * elements between the given start and end indices.
457   *
458   * <p>This defualt implementation will create a generic
459   * ViewletRange.  Subclasses can override this if specialised
460   * structures make more sense.  */
461  public ViewletRange createRange(List fromIndex, List toIndex) {
462    return new ViewletRangeCollection(allCombinations(fromIndex, toIndex));
463  }
464
465  protected class RangeUpdater implements Runnable {
466    ViewletRange range;
467    AbstractViewletDataStore store;
468    public RangeUpdater(AbstractViewletDataStore store, ViewletRange range) {
469      this.range = range;
470      this.store = store;
471    }
472    public void run() {
473      for(Iterator listenerIt =listenerList.iterator(); listenerIt.hasNext();) {
474        ((ViewletDataStoreListener)(listenerIt.next())).rangeUpdated(store,range);
475      }
476      if (range == null) {
477        store.fireTableDataChanged();
478        return;
479      }
480      for(Iterator it = range.iterator();
481          it.hasNext();
482          ) {
483        List index = (List)it.next();
484        if (index.size() == 1) {
485          int col = 0;
486          int row = ((Integer)index.get(0)).intValue()-1;
487          store.fireTableCellUpdated(row,col);
488        } else {
489          int row = ((Integer)index.get(0)).intValue()-1;
490          int col = ((Integer)index.get(1)).intValue()-1;
491          store.fireTableCellUpdated(row,col);
492        }
493      }
494    }
495  }
496
497  /** implments the symrefable interface*/
498  public void setSymRef(SymRef symRef) {
499    this.symRef = symRef;
500  }
501  /** implments the symrefable interface*/
502  public SymRef getSymRef() {
503    return this.symRef;
504  }
505
506}
507