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
32
33
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
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
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
253 viewTreeNode.setLabel(viewTreeNode.getLabel());
254
255
256 viewTreeNode.setUnselectedStyle(TRUNCATED_STYLE);
257 viewTreeNode.setSelectedStyle(TRUNCATED_STYLE);
258 }
259
260 try {
261
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
351
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
415
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
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
440
441
442 populateFromTransitionData(treeNode, buildPartTransition(part));
443
444
445
446
447
448
449
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
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
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
521
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
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 }