View Javadoc

1   package com.sri.emo.controller;
2   
3   import com.jcorporate.expresso.core.controller.ControllerException;
4   import com.jcorporate.expresso.core.controller.ExpressoResponse;
5   import com.jcorporate.expresso.core.controller.Transition;
6   import com.jcorporate.expresso.core.db.DBException;
7   import com.sri.common.controller.AbstractDBController;
8   import com.sri.common.taglib.DefaultTreeNode;
9   import com.sri.common.taglib.TreeNode;
10  import com.sri.emo.dbobj.*;
11  import com.sri.emo.dbobj.model_tree.*;
12  
13  import java.util.Iterator;
14  import java.util.Stack;
15  
16  /***
17   * TreeView Visitor transforms the model tree into a unified hiearchial tree
18   * that <tt>TreeMenuTag</tt> can understand for rendering a javascript tree.
19   * <p>Design-wise, it is useful because it separates all the view from the
20   * model: the tree model knows nothing about stylesheets, current URLs, etc</p>
21   * <p>This visitor is slightly different from the GoF pattern in that it is
22   * reponsible for traversing the data structure and maintaining its own
23   * state while traversing, although for clarification, the {@link com.sri.emo.dbobj.model_tree.ModelNode}
24   * provides an <tt>Iterator</tt> that is used.</p>
25   *
26   * @author Michael Rimov
27   */
28  public class TreeViewVisitor implements ModelVisitor, TreeTraversalListener {
29  
30      ////////////////////////////////////////////////////////////
31      //Section: CSS Styles used in TreeView
32      //Items are package access to that results can be tested
33      //with test cases.
34      ////////////////////////////////////////////////////////////
35  
36      /***
37       * CSS Style: Completed Node selected.
38       */
39      static final String FILLED_NODE_OPEN = "filled-node-open";
40  
41      /***
42       * CSS Style: Completed Node de-selected.
43       */
44      static final String FILLED_NODE_CLOSED = "filled-node-closed";
45  
46  
47      /***
48       * CSS Style: Partially-completed node selected.
49       */
50      static final String PARTIAL_NODE_OPEN = "partial-node-open";
51  
52      /***
53       * CSS Style: Partially-completed node de-selected.
54       */
55      static final String PARTIAL_NODE_CLOSED = "partial-node-closed";
56  
57  
58      /***
59       * CSS Style: un-completed node selected.
60       */
61      static final String EMPTY_NODE_OPEN = "empty-node-open";
62  
63      /***
64       * CSS Style: un-completed node de-selected.
65       */
66      static final String EMPTY_NODE_CLOSED = "empty-node-closed";
67  
68      /***
69       * CSS Style: single attribute selected.
70       */
71      static final String SINGLE_SELECTED = "selected";
72  
73      /***
74       * CSS Style: single attribute unselected.
75       */
76      static final String SINGLE_UNSELECTED = "subitem";
77  
78      /***
79       * CSS Style: multiple attribute completed selected.
80       */
81      static final String FILLED_SLOT_SELECTED = "filled-slot-open";
82  
83      /***
84       * CSS Style: multiple attribute completed unselected.
85       */
86      static final String FILLED_SLOT_CLOSED = "filled-slot-closed";
87  
88      /***
89       * CSS Style: multiple attribute not filled out selected.
90       */
91      static final String EMPTY_SLOT_SELECTED = "empty-slot-open";
92  
93      /***
94       * CSS Style: multiple attribute not filled out unselected.
95       */
96      static final String EMPTY_SLOT_UNSELECTED = "empty-slot-closed";
97  
98      /***
99       * CSS Style: multiple attribute partially filled selected.
100      */
101     static final String PARTIAL_SLOT_OPEN = "partial-slot-open";
102 
103     /***
104      * CSS Style: multiple attribute partially filled unselected.
105      */
106     static final String PARTIAL_SLOT_CLOSED = "partial-slot-closed";
107 
108     /***
109      * CSS Style: Truncated nodes
110      */
111     static final String TRUNCATED_STYLE = "truncated";
112 
113     //////////////////////////////////////////////////
114     // data members
115     ///////////////////////////////////////////////////////
116 
117     /***
118      * The root of the 'view tree' we're building.
119      */
120     DefaultTreeNode viewRoot = null;
121 
122     /***
123      *
124      */
125     private DefaultTreeNode viewCurrentLevel = null;
126 
127     /***
128      * Stack for traversing parent tree view nodes.  Contains DefaultTreeNode
129      * instances.
130      */
131     private Stack parentNodeStack = new Stack();
132 
133     /***
134      * Flag that is set when descendNode() messages are received.  It is
135      * reset as soon as constructChildNode() is called.  First node is
136      * ALWAYS child because we need to create a root node somewhere.
137      */
138     private boolean nextNodeIsChild = true;
139 
140     /***
141      * Represents the current node that we're about to visit to differentiate
142      * parts.  Allows for retrieval of other status book-keeping items we glean
143      * from the model node that we cannot glean from the individual data pieces
144      * from the wrapped items such as Node, Part, etc.
145      */
146     private ModelNode currentModelNode;
147 
148     /***
149      * The ControllerResponse object that we use to transform Transitions
150      * into links. (Usually HTML Links)
151      */
152     private final ExpressoResponse controllerResponse;
153 
154     /***
155      * Default constructor.
156      *
157      * @param response The <tt>ControllerResponse</tt> object for use
158      *                 in building Transitions.
159      */
160     public TreeViewVisitor(final ExpressoResponse response) {
161         assert response != null;
162         controllerResponse = response;
163     }
164 
165     /***
166      * Retrieve the tree that is suitable for viewing.that has been built.
167      *
168      * @return TreeNode
169      */
170     public TreeNode getTree() {
171         assert viewRoot != null;
172 
173         if (viewRoot == null) {
174             throw new IllegalStateException("Didn't receive any visit messages from any node.  " +
175                     "Tree not constructed");
176         }
177         return viewRoot;
178     }
179 
180     /***
181      * Visits an attribute.
182      *
183      * @param dataAttribute Attribute
184      */
185     public void visitAttribute(final Attribute dataAttribute) {
186         try {
187             DefaultTreeNode treeNode = constructViewNode();
188             Transition trans = dataAttribute.getViewTrans();
189             trans.addParam(AbstractDBController.EMBEDDED_MODE, "1");
190 
191             if (!dataAttribute.canRequesterWrite()) {
192                 trans.setControllerObject(NodeAction.class);
193                 trans.setState(NodeAction.VIEW_SINGLE_ATTRIBUTE);
194             }
195             populateFromTransitionData(treeNode, trans);
196 
197             // visiting a single attribute here
198             treeNode.setSelectedStyle(TreeViewVisitor.SINGLE_SELECTED);
199             treeNode.setUnselectedStyle(TreeViewVisitor.SINGLE_UNSELECTED);
200 //            }
201         } catch (Exception ex) {
202             throw new ViewVisitorException("Database Error visiting type: " + dataAttribute.getClass(), ex);
203         }
204 
205     }
206 
207     /***
208      * Sets the CSS Style for the tree node based on the fill status.
209      *
210      * @param treeNode DefaultTreeNode
211      */
212     private void setOvalCssStyle(final DefaultTreeNode treeNode) {
213         ModelFillStatus fillStatus = this.getCurrentModelNode().getModelFillStatus();
214 
215         if (fillStatus.equals(ModelFillStatus.COMPLETE)) {
216             treeNode.setSelectedStyle(TreeViewVisitor.FILLED_SLOT_SELECTED);
217             treeNode.setUnselectedStyle(TreeViewVisitor.FILLED_SLOT_CLOSED);
218         } else if (fillStatus.equals(ModelFillStatus.PARTIALLY_FILLED)) {
219             treeNode.setSelectedStyle(TreeViewVisitor.PARTIAL_SLOT_OPEN);
220             treeNode.setUnselectedStyle(TreeViewVisitor.PARTIAL_SLOT_CLOSED);
221         } else if (fillStatus.equals(ModelFillStatus.EMPTY)) {
222             treeNode.setSelectedStyle(TreeViewVisitor.EMPTY_SLOT_SELECTED);
223             treeNode.setUnselectedStyle(TreeViewVisitor.EMPTY_SLOT_UNSELECTED);
224         }
225     }
226 
227     /***
228      * Visits a Node.  Creates a new level inside the tree for each
229      * node visited.
230      *
231      * @param dataNode Node
232      */
233     public void visitNode(Node dataNode) {
234         DefaultTreeNode viewTreeNode = constructViewNode();
235         populateFromTransitionData(viewTreeNode, dataNode);
236         ModelFillStatus fillStatus = this.getCurrentModelNode().getModelFillStatus();
237 
238         if (fillStatus.equals(ModelFillStatus.COMPLETE)) {
239             viewTreeNode.setSelectedStyle(TreeViewVisitor.FILLED_NODE_OPEN);
240             viewTreeNode.setUnselectedStyle(TreeViewVisitor.FILLED_NODE_CLOSED);
241         } else if (fillStatus.equals(ModelFillStatus.PARTIALLY_FILLED)) {
242             viewTreeNode.setSelectedStyle(TreeViewVisitor.PARTIAL_NODE_OPEN);
243             viewTreeNode.setUnselectedStyle(TreeViewVisitor.PARTIAL_NODE_CLOSED);
244         } else if (fillStatus.equals(ModelFillStatus.EMPTY)) {
245             viewTreeNode.setSelectedStyle(TreeViewVisitor.EMPTY_NODE_OPEN);
246             viewTreeNode.setUnselectedStyle(TreeViewVisitor.EMPTY_NODE_CLOSED);
247         }
248 
249         NodeCompletionStatus completionStatus = this.getCurrentModelNode().getNodeCompletionStatus();
250         if (completionStatus == NodeCompletionStatus.TRUNCATED_NODE) {
251         	
252         	//????????????  Um, is this really doing anything?
253             viewTreeNode.setLabel(viewTreeNode.getLabel());
254             
255             
256             viewTreeNode.setUnselectedStyle(TRUNCATED_STYLE);
257             viewTreeNode.setSelectedStyle(TRUNCATED_STYLE);
258         }
259         
260         try {
261 	        //Add title, comments, etc for the node
262 	        DefaultTreeNode viewTitleNode = constructViewNode();
263 	        String nodeTitle = dataNode.getNodeTitle();
264 	        populateEditTransition(dataNode, viewTitleNode);
265 	        viewTitleNode.setLabel("Title");
266 	        if (nodeTitle != null && nodeTitle.length() == 0) {
267 	        	viewTitleNode.setSelectedStyle(TreeViewVisitor.EMPTY_SLOT_SELECTED);
268 	        	viewTitleNode.setUnselectedStyle(TreeViewVisitor.EMPTY_SLOT_UNSELECTED);
269 	        } else {
270 	        	viewTitleNode.setSelectedStyle(TreeViewVisitor.FILLED_SLOT_SELECTED);
271 	        	viewTitleNode.setUnselectedStyle(TreeViewVisitor.FILLED_SLOT_CLOSED);	        	
272 	        }
273 
274 	        DefaultTreeNode viewSummaryNode = constructViewNode();
275 	        populateEditTransition(dataNode, viewSummaryNode);
276 	        viewSummaryNode.setLabel("Summary");
277 	        String annotation = dataNode.getNodeAnnotation();
278 	        if (annotation != null && annotation.length() == 0) {
279 	        	viewSummaryNode.setSelectedStyle(TreeViewVisitor.EMPTY_SLOT_SELECTED);
280 	        	viewSummaryNode.setUnselectedStyle(TreeViewVisitor.EMPTY_SLOT_UNSELECTED);
281 	        } else {
282 	        	viewSummaryNode.setSelectedStyle(TreeViewVisitor.FILLED_SLOT_SELECTED);
283 	        	viewSummaryNode.setUnselectedStyle(TreeViewVisitor.FILLED_SLOT_CLOSED);	        	
284 	        }
285 	        
286 	        
287 	        DefaultTreeNode viewCommentNode = constructViewNode();
288 	        String nodeComment = dataNode.getNodeComment();
289 	        populateEditTransition(dataNode, viewCommentNode);
290 	        viewCommentNode.setLabel("Comment");
291 	        if (nodeComment != null && nodeComment.length() == 0) {
292 	        	viewCommentNode.setSelectedStyle(TreeViewVisitor.EMPTY_SLOT_SELECTED);
293 	        	viewCommentNode.setUnselectedStyle(TreeViewVisitor.EMPTY_SLOT_UNSELECTED);
294 	        } else {
295 	        	viewCommentNode.setSelectedStyle(TreeViewVisitor.FILLED_SLOT_SELECTED);
296 	        	viewCommentNode.setUnselectedStyle(TreeViewVisitor.FILLED_SLOT_CLOSED);	        	
297 	        }
298 	        
299 	        
300         } catch (DBException ex) {
301         	throw new ViewVisitorException("Error grabbing label, title or comment for node " + dataNode, ex);
302         }
303     }
304     
305     /***
306      * 
307      * @todo Hack: Directly attached to AddNodeAction.
308      * @param src
309      * @param treeNode
310      * @throws DBException
311      */
312     private void populateEditTransition(final Node src, final DefaultTreeNode treeNode) throws DBException {
313         Transition editTrans = new Transition();
314         editTrans.setControllerObject(AddNodeAction.class);
315         editTrans.setState(AddNodeAction.PROMPT_EDIT_NODE);
316         editTrans.addParam(AddNodeAction.EMBEDDED_MODE, "true");
317         editTrans.addParam(Node.NODE_ID, src.getNodeId());
318         populateFromTransitionData(treeNode, editTrans);
319     }
320     
321     /***
322      * Populates node common data from teh view transition and adapts exceptions.
323      *
324      * @param viewTreeNode DefaultTreeNode the tree node to populate with data.
325      * @param src          ModelVisitable the source of the data.
326      */
327     private void populateFromTransitionData(final DefaultTreeNode viewTreeNode, final ModelVisitable src) {
328         try {
329             Transition viewTransition = src.getViewTrans();
330             populateFromTransitionData(viewTreeNode, viewTransition);
331         } catch (DBException ex) {
332             throw new ViewVisitorException("Error visiting type: " + src.getClass(), ex);
333         }
334     }
335 
336     /***
337      * Extracts data for tree nodes from a given transition.
338      *
339      * @param viewTreeNode     DefaultTreeNode where to populate the data into.
340      * @param sourceTransition Transition the transition to query.
341      */
342     private void populateFromTransitionData(final DefaultTreeNode viewTreeNode, final Transition sourceTransition) {
343         assert viewTreeNode != null;
344         assert sourceTransition != null;
345 
346         try {
347             sourceTransition.setControllerResponse(controllerResponse);
348             viewTreeNode.setLabel(sourceTransition.getLabel());
349 
350             //We clone the transition before adding the embedded parameter so we don't affect the actual
351             //tree
352             viewTreeNode.setLink(sourceTransition.getFullUrl());
353         } catch (ControllerException ex) {
354             throw new ViewVisitorException("Error getting view transition from: " + sourceTransition.toString(), ex);
355         }
356     }
357 
358 
359     /***
360      * Constructs a new node, setting the appropriate parental settings,
361      * node types, and proper location inside the tree.
362      *
363      * @return DefaultTreeNode constructed node, properly placed in the tree
364      *         ready to have its
365      *         indidivudal properties set.
366      */
367     private DefaultTreeNode constructViewNode() {
368         if (nextNodeIsChild) {
369             nextNodeIsChild = false;
370             return constructChildViewingNode();
371         } else {
372             return constructSiblingViewingNode();
373         }
374     }
375 
376     /***
377      * Constructs a child node
378      *
379      * @return DefaultTreeNode
380      */
381     private DefaultTreeNode constructChildViewingNode() {
382         DefaultTreeNode current;
383         if (viewRoot == null) {
384             viewRoot = new DefaultTreeNode();
385             current = viewRoot;
386             viewCurrentLevel = viewRoot;
387         } else {
388             current = new DefaultTreeNode((DefaultTreeNode) parentNodeStack.peek());
389             viewCurrentLevel = current;
390         }
391 
392         parentNodeStack.push(current);
393         return current;
394     }
395 
396     /***
397      * Constructs a new viewing tree node that is a sibling of the current node.
398      *
399      * @return DefaultTreeNode instance set up as a sibling for the current view level.
400      */
401     private DefaultTreeNode constructSiblingViewingNode() {
402         return new DefaultTreeNode((DefaultTreeNode) viewCurrentLevel);
403     }
404 
405 
406     /***
407      * Moves up the tree so we follow the similar levels as the model tree.
408      */
409     private void moveUpViewTree() {
410         if (parentNodeStack.isEmpty()) {
411             throw new IllegalStateException("No parent nodes to move up to.");
412         }
413 
414         //Calculate how many nodes we need to move up until
415         //both the previous model node
416 
417         viewCurrentLevel = (DefaultTreeNode) parentNodeStack.pop();
418     }
419 
420     /***
421      * Visits a node type.  In this case, we don't create any view for the
422      * node types we encounter in the model so we do nothing.
423      *
424      * @param nodeType NodeType
425      * @throws ViewVisitorException upon error.
426      */
427     public void visitNodeType(final NodeType nodeType) {
428         //Do Nothing.
429     }
430 
431     /***
432      * Populates the view with a Part (or Slot)
433      *
434      * @param part Part
435      * @throws ViewVisitorException upon error.
436      */
437     public void visitPart(final Part part) {
438         DefaultTreeNode treeNode = constructViewNode();
439 //        try {
440 //            Node node = getParentDataNode();
441 //            if (node.canRequesterWrite()) {
442         populateFromTransitionData(treeNode, buildPartTransition(part));
443 //            } else {
444 //                // todo need better view trans
445 //                treeNode.setLabel(part.getPartLabel());
446 //                treeNode.setLink("#");   // todo can we have NO LINK for a treenode?
447 //            }
448 //        } catch (DBException e) {
449 //            part.getLogger().error(e);
450 //        }
451         setOvalCssStyle(treeNode);
452     }
453 
454     /***
455      * Retrieves the parent emo <tt>Node</tt> object.  This is only possible
456      * if we are visiting a <tt>Part</tt> or <tt>Attribute</tt> or <tt>Relation</tt>.
457      * Otherwise, this call may fail. (And will throw an Exception)
458      *
459      * @return Node
460      * @throws ViewVisitorException if unable to find a <tt>Node</tt> in
461      *                              any parents in the tree.
462      */
463     private Node getParentDataNode() {
464         for (ModelNode currentNode = this.getCurrentModelNode();
465              currentNode != ModelNode.NO_PARENT; currentNode = currentNode.getParent()) {
466 
467             if (currentNode.getVisitable() instanceof Node) {
468                 return (Node) currentNode.getVisitable();
469             }
470 
471         }
472 
473         //Never found one -- throw an exception
474         throw new ViewVisitorException("Unable to locate Node as parent of model node commanded to visit");
475     }
476 
477     /***
478      * Builds a transition to edit the part for the current tree node.
479      *
480      * @param currentPart Part the current part we're visiting.
481      * @return Transition constructed transition.
482      */
483     private Transition buildPartTransition(final Part currentPart) {
484         Transition result = null;
485 
486         try {
487 
488             Node node = getParentDataNode();
489             if (node.canRequesterWrite()) {
490                 result = currentPart.getEditTrans(node.getNodeId(),
491                         getControllerResponse().getExpressoRequest().getAllParameters());
492             } else {
493                 //We don't have permissions to edit a part.
494                 result = new Transition(currentPart.getPartLabel(), AddNodeAction.class, AddNodeAction.VIEW_PART);
495                 result.addParam(Node.NODE_ID, node.getNodeId());
496                 result.addParam(Part.PART_ID, currentPart.getId());
497             }
498 
499         } catch (Exception ex) {
500             throw new ViewVisitorException("Error building edit link for part: " + currentPart.toString(), ex);
501         }
502 
503         return result;
504     }
505 
506 
507     /***
508      * Visits a picklist.
509      *
510      * @param picklist PickList
511      */
512     public void visitPickList(final PickList picklist) {
513     }
514 
515     /***
516      * @param relation Relation
517      * @throws ViewVisitorException upon error.
518      */
519     public void visitRelation(final Relation relation) {
520         //A relation isn't rendered per-se:  The Model tree will conain
521         //child nodes that result because of the relations
522         //
523     }
524 
525 
526     /***
527      * Traverse the model tree.
528      *
529      * @param modelToTraverse Model
530      * @return TreeNode
531      */
532     public TreeNode traverseModelTree(final Model modelToTraverse) {
533         for (Iterator i = modelToTraverse.iterator(this); i.hasNext();) {
534             ModelNode oneNode = (ModelNode) i.next();
535             this.setCurrentModelNode(oneNode);
536             oneNode.getVisitable().acceptVisitor(this);
537         }
538 
539         return this.getTree();
540     }
541 
542     /***
543      * Registers descending through the tree.  In this case, a signal
544      * that the next node coming should be a child.
545      *
546      * @param newCurrentNode the new current node in the model that the iterator
547      *                       has descended to.
548      */
549     public void descendModelTree(final ModelNode newCurrentNode) {
550         if (shouldChangeViewLevel(newCurrentNode)) {
551             this.nextNodeIsChild = true;
552         }
553     }
554 
555     /***
556      * Registers a pop back up the tree.
557      *
558      * @param newCurrentNode the new current model node that the iterator
559      *                       as popped up to.
560      */
561     public void ascendModelTree(final ModelNode newCurrentNode) {
562         if (shouldChangeViewLevel(newCurrentNode)) {
563             moveUpViewTree();
564         }
565 //        moveUpViewTree();
566     }
567 
568 
569     /***
570      * Convoluted logic to decide if we should move up the view tree in
571      * reponse to an <tt>ascendTree</tt> event.
572      *
573      * @param newParentNode ModelNode
574      * @return boolean true if we should move up our own view tree.
575      */
576     private boolean shouldChangeViewLevel(final ModelNode newParentNode) {
577         ModelVisitable visitable = newParentNode.getVisitable();
578 
579         if (visitable instanceof Node) {
580             return true;
581         } else if (visitable instanceof Part) {
582             return true;
583         } else if (visitable instanceof Attribute) {
584             return true;
585         } else {
586             return false;
587         }
588     }
589 
590     public ModelNode getCurrentModelNode() {
591         return currentModelNode;
592     }
593 
594     public void setCurrentModelNode(final ModelNode nextModelNode) {
595         this.currentModelNode = nextModelNode;
596     }
597 
598     public ExpressoResponse getControllerResponse() {
599         return controllerResponse;
600     }
601 }