001    /*
002      Copyright (C) 2001-2003 Laurent Martelli <laurent@aopsys.com>
003      
004      This program is free software; you can redistribute it and/or modify
005      it under the terms of the GNU Lesser General Public License as
006      published by the Free Software Foundation; either version 2 of the
007      License, or (at your option) any later version.
008    
009      This program is distributed in the hope that it will be useful,
010      but WITHOUT ANY WARRANTY; without even the implied warranty of
011      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012      GNU Lesser General Public License for more details.
013    
014      You should have received a copy of the GNU Lesser General Public License
015      along with this program; if not, write to the Free Software
016      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
017    
018    package org.objectweb.jac.aspects.gui;
019    
020    import java.util.Arrays;
021    import java.util.Collection;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Vector;
026    import javax.swing.table.AbstractTableModel;
027    import org.apache.log4j.Logger;
028    import org.objectweb.jac.core.Collaboration;
029    import org.objectweb.jac.core.Wrappee;
030    import org.objectweb.jac.core.rtti.ClassItem;
031    import org.objectweb.jac.core.rtti.ClassRepository;
032    import org.objectweb.jac.core.rtti.CollectionItem;
033    import org.objectweb.jac.core.rtti.FieldItem;
034    import org.objectweb.jac.core.rtti.MemberItem;
035    import org.objectweb.jac.core.rtti.MetaItem;
036    import org.objectweb.jac.core.rtti.MethodItem;
037    import org.objectweb.jac.core.rtti.RttiAC;
038    import org.objectweb.jac.util.Classes;
039    import org.objectweb.jac.util.Enum;
040    import org.objectweb.jac.util.ExtArrays;
041    import org.objectweb.jac.util.Stack;
042    
043    /**
044     * The data model for tables. */
045    
046    public class TableModel extends AbstractTableModel
047        implements ExtendedTableModel, ObjectUpdate, CollectionUpdate
048    {
049        static Logger logger = Logger.getLogger("gui.generic");
050        static Logger loggerTable = Logger.getLogger("gui.table");
051        static Logger loggerAssoc = Logger.getLogger("gui.events");
052        static Logger loggerEvents = Logger.getLogger("associations");
053        static Logger loggerReg = Logger.getLogger("gui.register");
054    
055        List rows = new Vector();
056        List objects = new Vector();
057        String[] headers;
058        ClassItem[] classes;
059        MemberItem[] members;
060    
061        CollectionItem collection;
062        Object substance;
063    
064        CollectionItemView collectionView;
065    
066        /**
067         * Creates a new table model.
068         *
069         * @param collection the substance collection
070         * @param substance the object that holds the collection
071         * @param factory the used view factory 
072         */
073        public TableModel(CollectionItem collection, 
074                          Object substance, String viewName,
075                          ViewFactory factory)
076        {
077            this.collection = collection;
078            this.substance = substance;
079            collectionView = GuiAC.getView(collection,viewName);
080    
081            members = collectionView.getMembersOrder();
082            logger.debug("membersOrder : " + 
083                      (members!=null?(Arrays.asList(members).toString()):"[]"));
084            if (members==null) {
085                ClassItem clit = collection.getComponentType();
086                if (clit!=null  && !(collection.isMap() && !RttiAC.isIndex(collection))) {
087                    logger.debug("class : " + clit.getName());
088                    ObjectView objectView = GuiAC.getView(clit,viewName);
089                    if (members==null)
090                        members = objectView.getTableMembersOrder();
091                    logger.debug("class tableMembersOrder : " + 
092                              (members!=null?(Arrays.asList(members)+""):""));
093                    if (members==null)
094                        members = (MemberItem[])objectView.getAttributesOrder();
095                    logger.debug("class attributesOrder : " +
096                              (members!=null?(Arrays.asList(members)+""):""));
097                    if (members==null)
098                        members = clit.getFields();
099                } else if (!RttiAC.isIndex(collection)) {
100                    members = new MemberItem[2];
101                    clit = ClassRepository.get().getClass(Map.Entry.class);
102                    members[0] = clit.getField("key");
103                    members[1] = clit.getField("value");
104                } else {
105                    members = new MemberItem[0];
106                }
107            }
108            logger.debug("columns : " + Arrays.asList(members));
109    
110            FieldItem oppositeRole = 
111                (FieldItem)Collaboration.get().getAttribute(GuiAC.OPPOSITE_ROLE);
112            if (oppositeRole instanceof CollectionItem) {
113                loggerAssoc.debug("Ignoring collection oppositeRole "+oppositeRole);
114                oppositeRole = null;
115            }
116    
117            // Ensure that members contains no oppositeRole
118            MemberItem[] tmp = new MemberItem[members.length];
119            int j=0;
120            for(int i=0; i<members.length; i++) {
121                if (members[i]!=oppositeRole) {
122                    tmp[j] = members[i];
123                    j++;
124                }
125            }
126            members = new MemberItem[j];
127            System.arraycopy(tmp,0,members,0,j);
128    
129            int nbColumns = members.length;
130            headers = new String[nbColumns];
131            classes = new ClassItem[nbColumns];
132    
133            Stack context = GuiAC.getGraphicContext();
134            for (int i=0; i<nbColumns; i++) {
135                if (members[i] instanceof FieldItem) {
136                    headers[i] = GuiAC.getLabel(members[i],context);
137                }
138                classes[i] = members[i].getTypeItem();
139            }
140    
141            buildData();
142            Utils.registerCollection(substance,collection,this);
143        }
144    
145        public CollectionItem getCollection() {
146            return collection;
147        }
148    
149        static class CellLocation {
150            int row;
151            int column;
152            public CellLocation(int row, int column) {
153                this.row = row;
154                this.column = column;
155            }
156        }
157    
158        public Object getCellRenderer(View tableView, int column, 
159                                      ViewFactory factory, DisplayContext context) {
160            return getCellRenderer(tableView,substance,
161                                   members[column],headers[column],
162                                   collectionView,
163                                   factory,context);
164        }
165    
166        public static Object getCellRenderer(View tableView, 
167                                             Object substance,
168                                             MemberItem member, 
169                                             String header,
170                                             CollectionItemView collectionView,
171                                             ViewFactory factory, 
172                                             DisplayContext context) 
173        {
174            if (member instanceof FieldItem) {
175                FieldItem field = (FieldItem)member;
176                Stack typeNames = new Stack();
177    
178                if (field.isReference())
179                    typeNames.push("cell:Reference");
180                else if (field instanceof CollectionItem)
181                    typeNames.push("cell:List");
182                typeNames.push("cell:"+field.getTypeItem().getName());
183    
184                MetaItem type = RttiAC.getFieldType(field);
185                if (type!=null)
186                    typeNames.push("cell:"+type.getName());
187    
188                Enum enum = GuiAC.getEnum(field);
189                if (enum!=null)
190                    typeNames.push("cell:Enum");
191    
192                if (collectionView!=null) {
193                    String viewType = collectionView.getViewType(field);
194                    if (viewType!=null) {
195                        typeNames.push(viewType);
196                    }
197                }
198    
199                loggerTable.debug("types for "+field+": "+typeNames);
200                while (!typeNames.empty()) {
201                    String typeName = (String)typeNames.pop();
202                    if (factory.hasViewerFor(typeName)) {
203                        try {
204                            FieldView cellViewer = (FieldView)factory.createView(
205                                "table["+field.getName()+"]",
206                                typeName,
207                                ExtArrays.emptyObjectArray,context);
208                            if (cellViewer instanceof TableCellViewer) {
209                                ((TableCellViewer)cellViewer).setTable(tableView);
210                            }
211                            loggerTable.debug("viewer = "+cellViewer);
212                            cellViewer.setField(field);
213                            return cellViewer;
214                        } catch (Exception e) {
215                            logger.error("Failed to instanciate TableCellRenderer "+
216                                         field.getName()+" for column "+header,
217                                         e);
218                        }
219                    } else {
220                        loggerTable.debug("no viewer found for type "+typeName);
221                    }
222                }
223            } else if (member instanceof MethodItem) {
224                MethodItem method = (MethodItem)member;
225                try {
226                    MethodView cellViewer = (MethodView)factory.createView(
227                        "table["+method.getName()+"]",
228                        "cell:Method",
229                        new Object[] {substance, method},context);
230                    if (cellViewer instanceof TableCellViewer) {
231                        ((TableCellViewer)cellViewer).setTable(tableView);
232                    }
233                    loggerTable.debug("viewer = "+cellViewer);
234                    return cellViewer;
235                } catch (Exception e) {
236                    logger.error("Failed to instanciate TableCellRenderer "+
237                                 method.getName()+" for column "+header,
238                                 e);
239                }
240            }
241            return null;
242        }
243    
244        /**
245         * Populate the table model with the objects from the collection.
246         */
247        void buildData() {
248            Collection c = collection.getActualCollectionThroughAccessor(substance);
249            logger.debug("substance : " + substance);
250            logger.debug("objects : " + new Vector(c));
251            int nbColumns = members.length;
252            Iterator i = c.iterator();
253            int row = 0;
254            while (i.hasNext()) {
255                Object obj = i.next();
256                Object[] data = new Object[nbColumns];
257                if (obj!=null) {
258                    for (int j=0; j<nbColumns; j++) {
259                        try {
260                            if (members[j] instanceof FieldItem) { 
261                                data[j] = ((FieldItem)members[j]).getThroughAccessor(obj);
262                                if (data[j] instanceof Wrappee && !(data[j] instanceof Collection)) {
263                                    Utils.registerObject(data[j],this,new CellLocation(row,j));
264                                }
265                            } else if (members[j] instanceof MethodItem) {
266                                data[j] = members[j].getName();
267                            }
268                        } catch (Exception e) {
269                            logger.error("Failed to build table cell for "+obj+"."+members[j].getName(),e);
270                        }
271                    }
272                }
273                logger.debug("add "+Arrays.asList(data));
274                addRow(obj,data);
275                row++;
276            }      
277        }
278    
279        public MemberItem[] getMembers() {
280            return members;
281        }
282    
283        public String[] getHeaders() {
284            return headers;
285        }
286    
287        /**
288         * Gets the column number of a field
289         */
290        public int getColumnIndex(FieldItem field) {
291            for (int i=0; i<members.length; i++) {
292                if (field==members[i])
293                    return i;
294            }
295            return -1;
296        }
297    
298        public int getRowCount() {
299            return rows.size();
300        }
301        public int getColumnCount() {
302            return headers.length;
303        }
304    
305        public String getColumnName(int column) {
306            return headers[column];
307        }
308        public Class getColumnClass(int column) {
309            loggerTable.debug("getColumnClass("+column+") -> "+classes[column].getName());
310            return Classes.getPrimitiveTypeWrapper(classes[column].getActualClass());
311        }
312        public Object getValueAt(int row, int column) {
313            loggerTable.debug("getValueAt("+row+","+column+") -> "+
314                      ((Object[])rows.get(row))[column]);
315            return ((Object[])rows.get(row))[column];
316        }
317        public Object getObject(int index) {
318            return objects.get(index);
319        }
320        public int indexOf(Object object) {
321            return objects.indexOf(object);
322        }
323        public Object getObject(int row, int col) {
324            return ((Object[])rows.get(row))[col];
325        }
326        public Object[] getRow(int row) {
327            return (Object[])rows.get(row);
328        }
329        public boolean isCellEditable(int row, int column) {
330            return false;
331        }
332    
333        public void addRow(Object object, Object[] data) {
334            objects.add(object);
335            rows.add(data);
336            Utils.registerObject(object,this);
337        }
338    
339        // ObjectUpdate interface
340        public void objectUpdated(Object substance,Object param) {
341            if (param==null) {
342                int index = objects.indexOf(substance);
343                if (index!=-1) {
344                    int nbColumns = getColumnCount();
345                    Object[] data = new Object[nbColumns];
346                    for (int j=0; j<nbColumns; j++) {
347                        if(members[j] instanceof FieldItem) {
348                            data[j] = ((FieldItem)members[j]).getThroughAccessor(substance);
349                        }
350                    }
351                    rows.set(index,data);
352                }
353                fireTableRowsUpdated(index,index);
354            } else {
355                CellLocation location = (CellLocation)param;
356                fireTableCellUpdated(location.row,location.column);
357            }
358        }
359    
360        // CollectionUpdate
361        public void onChange(Object substance, CollectionItem collection, 
362                             Object value, Object param) {
363            loggerEvents.debug("onChange "+substance+"."+collection);
364            unregisterViews();
365            int numRows = objects.size();
366            loggerEvents.debug("  numRows="+numRows);
367            objects.clear();
368            rows.clear();
369            if (numRows>0)
370                fireTableRowsDeleted(0,numRows-1);
371            buildData();
372            fireTableRowsInserted(0,objects.size()-1);
373        }
374    
375        public void onAdd(Object substance, CollectionItem collection, 
376                          Object value, Object added, Object param) {
377            loggerEvents.debug("onAdd "+substance+"."+collection);
378            // it's not that easy to optimize because we don't know the
379            // position of the added object
380            onChange(substance,collection,value,param);
381        }
382    
383        public void onRemove(Object substance, CollectionItem collection, 
384                             Object value, Object removed, Object param) {
385            loggerEvents.debug("onRemove "+substance+"."+collection);
386            onChange(substance,collection,value,param);
387        }
388    
389        /**
390         * Register ourself as a view on all objects of the collection
391         */
392        protected void registerViews() {
393            loggerReg.debug("TableModel.registerViews "+objects.size());
394            Iterator i = objects.iterator();
395            while (i.hasNext()) {
396                Object object = i.next();
397                Utils.registerObject(object,this);
398            }
399        }
400    
401        /**
402         * Unregister ourself as a view on all objects of the collection
403         */
404        protected void unregisterViews() {
405            loggerReg.debug("TableModel.unRegisterViews "+objects.size());
406            Iterator i = objects.iterator();
407            while (i.hasNext()) {
408                Object object = i.next();
409                Utils.unregisterObject(object,this);
410            }      
411        }
412    
413        public void close() {
414            unregisterViews();
415            Utils.unregisterCollection(substance,collection,this);
416        }
417    
418        public TableFilter getFilter() {
419            return null;
420        }
421        public TableSorter getSorter() {
422            return null;
423        }
424    }