001    /*
002      Copyright (C) 2002-2003 Renaud Pawlak <renaud@aopsys.com>,
003                              Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful, but
011      WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013      Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public
016      License along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
018      USA */
019    
020    package org.objectweb.jac.aspects.gui;
021    
022    import java.util.Hashtable;
023    import java.util.Iterator;
024    import java.util.Map;
025    import javax.swing.tree.MutableTreeNode;
026    import javax.swing.tree.TreePath;
027    import org.apache.log4j.Logger;
028    import org.objectweb.jac.core.Collaboration;
029    import org.objectweb.jac.core.rtti.ClassRepository;
030    import org.objectweb.jac.core.rtti.CollectionItem;
031    import org.objectweb.jac.core.rtti.FieldItem;
032    import org.objectweb.jac.util.Stack;
033    
034    /**
035     * This class represents a tree node for an object. */
036    
037    public class ObjectNode extends AbstractNode 
038        implements ObjectUpdate, FieldUpdate, CollectionUpdate 
039    {
040        static Logger logger = Logger.getLogger("gui.treeview");
041        static Logger loggerEvents = Logger.getLogger("gui.events");
042        static Logger loggerCol = Logger.getLogger("gui.collectionupdate");
043    
044        FieldItem relation;
045        Object substance;
046    
047        /**
048         * Constructor.
049         *
050         * @param model the tree modelto notify when changes occur
051         * @param value the value that the node represents
052         * @param substance the owner of the relation
053         * @param relation the relation the value is part of
054         */
055        public ObjectNode(TreeView model, Object value, 
056                          Object substance, FieldItem relation, 
057                          boolean showRelations) 
058        {
059            super(model,value, showRelations);
060            this.substance = substance;
061            this.relation = relation;
062            Utils.registerObject(value,this);
063            if (!(relation instanceof CollectionItem)) {
064                Utils.registerField(value,relation,this);
065            }
066            if (GuiAC.getGraphicContext()!=null)
067                context.addAll(GuiAC.getGraphicContext());
068            if (relation!=null)
069                context.push(relation);
070            rebuildData();
071        }
072    
073        /** FieldItem -> Integer(Index of firt node of the relation) */
074        Hashtable relationIndices;
075    
076        /**
077         * Insert a node at the correct place (considering sorting)
078         * @param node the node to insert
079         * @return the position the node was inserted at
080         */
081        public int addNode(ObjectNode node) {
082            logger.debug("Inserting "+node.getText()+"...");
083            FieldItem relation = node.getRelation();
084            if (relationIndices==null)
085                relationIndices = new Hashtable();
086            Integer relIndex = (Integer)relationIndices.get(relation);
087            if (relIndex==null) {
088                relIndex = new Integer(getChildCount());
089                relationIndices.put(relation,relIndex);
090            }
091            // Find where to insert the node (sort according to text)
092            int i;
093            for (i=relIndex.intValue(); i<getChildCount(); i++) {
094                ObjectNode current = (ObjectNode)getChildAt(i);
095                logger.debug("  current "+current);
096                if (current.getText().compareToIgnoreCase(node.getText())>0)
097                    break;
098            }
099            logger.debug("Inserting "+node.getText()+" at "+relation.getName()+"["+i+"]");
100            insert(node, i);
101            
102            // Update relation start indices
103            Iterator it = relationIndices.entrySet().iterator();
104            while (it.hasNext()) {
105                Map.Entry entry = (Map.Entry)it.next();
106                int index = ((Integer)entry.getValue()).intValue();
107                if (index>=i && entry.getKey()!=relation) {
108                    entry.setValue(new Integer(index+1));
109                }
110            }
111            return i;
112        }
113    
114        public void removeAllChildren() {
115            super.removeAllChildren();
116            if (relationIndices!=null)
117                relationIndices.clear();
118        }
119    
120        Stack context = new Stack();
121    
122        /**
123         * Returns the relation (reference or collection) the substance of
124         * the node belongs to. */
125        public FieldItem getRelation() {
126            return relation;
127        }
128    
129        /**
130         * Returns the substance of this node. */
131        public Object getSubstance() {
132            return substance;
133        }
134    
135        /**
136         * Rebuild the data of this node again. */
137    
138        protected void rebuildData() {
139            Object object = getUserObject();
140            logger.debug("refresh("+object+")");
141            if (object==null) {
142                icon = null;
143                text = "<null>";
144                tooltip = null;
145            } else {
146                text = GuiAC.toString(object,context);
147                tooltip = GuiAC.getToolTip(object,context);
148                logger.debug("text="+text);
149                icon = GuiAC.getIcon(ClassRepository.get().getClass(object),object);
150            }
151        }
152    
153        /**
154         * Unregisters from the events this node is notified. */
155    
156        public void unregisterEvents() {
157            Object value = getUserObject();
158            Utils.unregister(value,this);
159        }
160    
161        /**
162         * Find a node in the children.
163         * @param relation the relation the requested node must be part of
164         * @param userObject the userObject value the requested node must have
165         * @return an ObjectNode with the requested features, or null if no
166         * such node can be found.
167         */
168        protected ObjectNode findNode(FieldItem relation, Object userObject) {
169            logger.debug("Looking for node "+relation+" -> "+userObject);
170            for (int i=0; i<getChildCount(); i++) {
171                if (getChildAt(i) instanceof ObjectNode) {
172                    ObjectNode current = (ObjectNode)getChildAt(i);
173                    if (current.getUserObject()==userObject &&
174                        current.getRelation()==relation)
175                        return current;
176                }
177            }
178            return null;
179        }
180    
181        // ObjectUpdate interface
182    
183        public void objectUpdated(Object substance, Object param) {
184            rebuildData();
185            model.nodeChanged(this);
186        }
187    
188        // FieldUpdate interface
189    
190        public void fieldUpdated(Object substance, FieldItem collection, 
191                                 Object value, Object param) {
192            logger.debug("fieldUpdated on "+this);
193            rebuildData();
194            TreeModel.addNodes(model,this,getUserObject(),showRelations);
195            ObjectNode newNode = findNode(collection,value);
196            //model.nodesWereInserted(this,indices);
197            if (newNode!=null) {
198                model.setSelection(new TreePath(newNode.getPath()));
199            }
200            model.nodeChanged(this);
201        }
202        
203        // CollectionUpdate interface
204    
205        public void onChange(Object substance, CollectionItem collection, 
206                             Object value, Object param) {
207            loggerCol.debug("ObjectNode collectionUpdated "+collection.getLongName());
208            // remove all children nodes
209            int[] indices = new int[getChildCount()];
210            AbstractNode[] removedNodes = new AbstractNode[getChildCount()];
211            for(int i=0; i<indices.length; i++) {
212                indices[i] = i;
213                removedNodes[i] = (AbstractNode)getChildAt(i);
214            }
215            removeAllChildren();
216            model.nodesWereRemoved(this,indices,removedNodes);
217    
218            // rebuild children nodes
219            TreeModel.addNodes(model,this,getUserObject(),showRelations);
220    
221            indices = new int[getChildCount()];
222            for(int i=0;i<indices.length;i++) {
223                indices[i] = i;
224            }
225            model.nodesWereInserted(this,indices);
226        }
227    
228        public void onAdd(Object substance, CollectionItem collection, 
229                          Object value, Object added, Object param) {
230            loggerCol.debug("onAdd "+collection.getLongName()+" "+added+" - "+areChildrenUptodate);
231            ObjectNode newNode;
232            if (areChildrenUptodate) {
233                newNode = new ObjectNode(model,added,substance,collection,showRelations); 
234                newNode.setLeaf(TreeModel.isLeafNode(model,newNode,added,showRelations));
235                int pos = addNode(newNode);
236                model.nodesWereInserted(this,new int[] {pos});
237                setChildrenUptodate(true);
238            } else {
239                TreeModel.addNodes(model,this,getUserObject(),showRelations);
240                int[] indices = new int[getChildCount()];
241                for(int i=0; i<indices.length; i++) {
242                    indices[i] = i;
243                }
244                newNode = findNode(collection,added);
245                model.nodesWereInserted(this,indices);
246            }
247            if (newNode!=null) {
248                model.setSelection(new TreePath(newNode.getPath()));
249            }
250        }
251    
252        public void onRemove(Object substance, CollectionItem collection, 
253                             Object value, Object removed, Object param) {
254            loggerCol.debug("onRemove "+collection.getLongName()+" "+removed);
255            AbstractNode removedNode = 
256                (AbstractNode)Collaboration.get().getAttribute(GuiAC.REMOVED_NODE);
257            if (removedNode!=null) {
258                loggerEvents.debug("removing = "+removedNode+" from "+this);
259                int removedIndex = getIndex(removedNode);
260                remove(removedNode);
261                model.nodesWereRemoved(
262                    this,
263                    new int[] {removedIndex},
264                    new Object[] {removedNode});
265            } else {
266                onChange(substance,collection,value,param);
267            }
268        }
269    
270        public void updateChildren() {
271            if (!areChildrenUptodate()) {
272                loggerEvents.debug("updateChildren "+this);
273                TreeModel.addNodes(model,this,getUserObject(),showRelations);
274                int[] indices = new int[getChildCount()];
275                for(int i=0; i<indices.length; i++) {
276                    indices[i] = i;
277                }
278                model.nodesWereInserted(this,indices);
279                setChildrenUptodate(true);
280            } else {
281                loggerEvents.debug("children are uptodate for "+this);
282            }
283        }
284    
285        public String toString() {
286            return substance+"."+relation+" -> "+getUserObject();
287        }
288    }
289