Coverage Report - org.kuali.rice.krms.impl.ui.AgendaEditorController
 
Classes in this File Line Coverage Branch Coverage Complexity
AgendaEditorController
0%
0/616
0%
0/350
3.688
AgendaEditorController$1
0%
0/1
N/A
3.688
AgendaEditorController$AgendaItemChildAccessor
0%
0/27
0%
0/16
3.688
AgendaEditorController$AgendaItemChildAccessor$Child
0%
0/1
N/A
3.688
AgendaEditorController$AgendaItemInstanceChildAccessor
0%
0/8
N/A
3.688
 
 1  
 /**
 2  
  * Copyright 2005-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.krms.impl.ui;
 17  
 
 18  
 import org.apache.commons.lang.StringUtils;
 19  
 import org.kuali.rice.core.api.util.tree.Node;
 20  
 import org.kuali.rice.krad.document.MaintenanceDocument;
 21  
 import org.kuali.rice.krad.service.KRADServiceLocator;
 22  
 import org.kuali.rice.krad.service.SequenceAccessorService;
 23  
 import org.kuali.rice.krad.uif.UifParameters;
 24  
 import org.kuali.rice.krad.util.ObjectUtils;
 25  
 import org.kuali.rice.krad.web.controller.MaintenanceDocumentController;
 26  
 import org.kuali.rice.krad.web.form.MaintenanceForm;
 27  
 import org.kuali.rice.krad.web.form.UifFormBase;
 28  
 import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
 29  
 import org.kuali.rice.krms.impl.repository.ActionAttributeBo;
 30  
 import org.kuali.rice.krms.impl.repository.ActionBo;
 31  
 import org.kuali.rice.krms.api.repository.LogicalOperator;
 32  
 import org.kuali.rice.krms.api.repository.proposition.PropositionType;
 33  
 import org.kuali.rice.krms.impl.repository.AgendaBo;
 34  
 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
 35  
 import org.kuali.rice.krms.impl.repository.ContextBoService;
 36  
 import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionBo;
 37  
 import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
 38  
 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
 39  
 import org.kuali.rice.krms.impl.repository.PropositionBo;
 40  
 import org.kuali.rice.krms.impl.repository.RuleBo;
 41  
 import org.kuali.rice.krms.impl.rule.AgendaEditorBusRule;
 42  
 import org.springframework.stereotype.Controller;
 43  
 import org.springframework.validation.BindingResult;
 44  
 import org.springframework.web.bind.annotation.ModelAttribute;
 45  
 import org.springframework.web.bind.annotation.RequestMapping;
 46  
 import org.springframework.web.servlet.ModelAndView;
 47  
 
 48  
 import javax.servlet.http.HttpServletRequest;
 49  
 import javax.servlet.http.HttpServletResponse;
 50  
 import java.util.ArrayList;
 51  
 import java.util.HashMap;
 52  
 import java.util.HashSet;
 53  
 import java.util.List;
 54  
 import java.util.Map;
 55  
 import java.util.Set;
 56  
 
 57  
 /**
 58  
  * Controller for the Test UI Page
 59  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 60  
  */
 61  
 @Controller
 62  
 @RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_EDITOR_PATH)
 63  0
 public class AgendaEditorController extends MaintenanceDocumentController {
 64  
 
 65  
     private SequenceAccessorService sequenceAccessorService;
 66  
 
 67  
     /**
 68  
      * This overridden method does extra work on refresh to update the namespace when the context has been changed.
 69  
      *
 70  
      * @see org.kuali.rice.krad.web.controller.UifControllerBase#refresh(org.kuali.rice.krad.web.form.UifFormBase, org.springframework.validation.BindingResult, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 71  
      */
 72  
     @RequestMapping(params = "methodToCall=" + "refresh")
 73  
     @Override
 74  
     public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 75  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 76  0
         ModelAndView modelAndView = super.refresh(form, result, request, response);
 77  
 
 78  
         // handle return from context lookup and update the namespace on all agenda related objects if the contest has been changed
 79  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 80  0
         AgendaEditor agendaEditor = ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
 81  0
         if (!StringUtils.equals(agendaEditor.getOldContextId(), agendaEditor.getAgenda().getContextId())) {
 82  0
             agendaEditor.setOldContextId(agendaEditor.getAgenda().getContextId());
 83  
 
 84  0
             String namespace = "";
 85  0
             if (!StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
 86  0
                 namespace = getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace();
 87  
             }
 88  
 
 89  0
             for (AgendaItemBo agendaItem : agendaEditor.getAgenda().getItems()) {
 90  0
                 agendaItem.getRule().setNamespace(namespace);
 91  0
                 for (ActionBo action : agendaItem.getRule().getActions()) {
 92  0
                     action.setNamespace(namespace);
 93  
                 }
 94  
             }
 95  
         }
 96  0
         return modelAndView;
 97  
     }
 98  
 
 99  
     /**
 100  
      * This method updates the existing rule in the agenda.
 101  
      */
 102  
     @RequestMapping(params = "methodToCall=" + "goToAddRule")
 103  
     public ModelAndView goToAddRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 104  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 105  0
         setAgendaItemLine(form, null);
 106  
 
 107  0
         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
 108  0
         return super.navigate(form, result, request, response);
 109  
     }
 110  
 
 111  
     /**
 112  
      * This method sets the agendaItemLine for adding/editing AgendaItems.
 113  
      * The agendaItemLine is a copy of the agendaItem so that changes are not applied when
 114  
      * they are abandoned.  If the agendaItem is null a new empty agendaItemLine is created.
 115  
      *
 116  
      * @param form
 117  
      * @param agendaItem
 118  
      */
 119  
     private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) {
 120  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 121  0
         if (agendaItem == null) {
 122  0
             RuleBo rule = new RuleBo();
 123  0
             if (StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
 124  0
                 rule.setNamespace("");
 125  
             } else {
 126  0
                 rule.setNamespace(getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace());
 127  
             }
 128  0
             agendaItem = new AgendaItemBo();
 129  0
             agendaItem.setRule(rule);
 130  0
             agendaEditor.setAgendaItemLine(agendaItem);
 131  0
         } else {
 132  
             // TODO: Add a copy not the reference
 133  0
             agendaEditor.setAgendaItemLine((AgendaItemBo) ObjectUtils.deepCopy(agendaItem));
 134  
         }
 135  
 
 136  
 
 137  0
         if (agendaItem.getRule().getActions().isEmpty()) {
 138  0
             ActionBo actionBo = new ActionBo();
 139  0
             actionBo.setNamespace(agendaItem.getRule().getNamespace());
 140  0
             actionBo.setRuleId(agendaItem.getRule().getId());
 141  0
             actionBo.setSequenceNumber(1);
 142  0
             agendaEditor.setAgendaItemLineRuleAction(actionBo);
 143  0
         } else {
 144  0
             agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0));
 145  
         }
 146  
 
 147  0
         agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes());
 148  0
     }
 149  
 
 150  
     /**
 151  
      * This method returns the agendaItemLine from adding/editing AgendaItems.
 152  
      *
 153  
      * @param form
 154  
      * @return agendaItem
 155  
      */
 156  
     private AgendaItemBo getAgendaItemLine(UifFormBase form) {
 157  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 158  0
         return agendaEditor.getAgendaItemLine();
 159  
     }
 160  
 
 161  
     /**
 162  
      * This method returns the id of the selected agendaItem.
 163  
      *
 164  
      * @param form
 165  
      * @return selectedAgendaItemId
 166  
      */
 167  
     private String getSelectedAgendaItemId(UifFormBase form) {
 168  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 169  0
         return agendaEditor.getSelectedAgendaItemId();
 170  
     }
 171  
 
 172  
     /**
 173  
      * This method sets the id of the cut agendaItem.
 174  
      *
 175  
      * @param form
 176  
      * @param cutAgendaItemId
 177  
      */
 178  
     private void setCutAgendaItemId(UifFormBase form, String cutAgendaItemId) {
 179  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 180  0
         agendaEditor.setCutAgendaItemId(cutAgendaItemId);
 181  0
     }
 182  
 
 183  
     /**
 184  
      * This method returns the id of the cut agendaItem.
 185  
      *
 186  
      * @param form
 187  
      * @return cutAgendaItemId
 188  
      */
 189  
     private String getCutAgendaItemId(UifFormBase form) {
 190  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 191  0
         return agendaEditor.getCutAgendaItemId();
 192  
     }
 193  
 
 194  
     /**
 195  
      * This method updates the existing rule in the agenda.
 196  
      */
 197  
     @RequestMapping(params = "methodToCall=" + "goToEditRule")
 198  
     public ModelAndView goToEditRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 199  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 200  
 
 201  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 202  
         // this is the root of the tree:
 203  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 204  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 205  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 206  
 
 207  0
         setAgendaItemLine(form, node);
 208  
 
 209  0
         form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
 210  0
         return super.navigate(form, result, request, response);
 211  
     }
 212  
 
 213  
     /**
 214  
      *  This method adds the newly create rule to the agenda.
 215  
      */
 216  
     @RequestMapping(params = "methodToCall=" + "addRule")
 217  
     public ModelAndView addRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 218  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 219  
 
 220  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 221  0
         AgendaBo agenda = agendaEditor.getAgenda();
 222  0
         AgendaItemBo newAgendaItem = agendaEditor.getAgendaItemLine();
 223  
 
 224  0
         updateRuleAction(agendaEditor);
 225  
 
 226  0
         if (agenda.getItems() == null) {
 227  0
             agenda.setItems(new ArrayList<AgendaItemBo>());
 228  
         }
 229  
 
 230  0
         AgendaEditorBusRule rule = new AgendaEditorBusRule();
 231  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 232  0
         MaintenanceDocument document = maintenanceForm.getDocument();
 233  0
         if (rule.processAgendaItemBusinessRules(document)) {
 234  0
             newAgendaItem.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_ITM_S")
 235  
                     .toString());
 236  0
             newAgendaItem.setAgendaId(getCreateAgendaId(agenda));
 237  0
             if (agenda.getFirstItemId() == null) {
 238  0
                 agenda.setFirstItemId(newAgendaItem.getId());
 239  
             } else {
 240  
                 // insert agenda in tree
 241  0
                 String selectedAgendaItemId = getSelectedAgendaItemId(form);
 242  0
                 if (StringUtils.isBlank(selectedAgendaItemId)) {
 243  
                     // add after the last root node
 244  0
                     AgendaItemBo node = getFirstAgendaItem(agenda);
 245  0
                     while (node.getAlways() != null) {
 246  0
                         node = node.getAlways();
 247  
                     }
 248  0
                     node.setAlwaysId(newAgendaItem.getId());
 249  0
                     node.setAlways(newAgendaItem);
 250  0
                 } else {
 251  
                     // add after selected node
 252  0
                     AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 253  0
                     AgendaItemBo node = getAgendaItemById(firstItem, selectedAgendaItemId);
 254  0
                     newAgendaItem.setAlwaysId(node.getAlwaysId());
 255  0
                     newAgendaItem.setAlways(node.getAlways());
 256  0
                     node.setAlwaysId(newAgendaItem.getId());
 257  0
                     node.setAlways(newAgendaItem);
 258  
                 }
 259  
             }
 260  
             // add it to the collection on the agenda too
 261  0
             agenda.getItems().add(newAgendaItem);
 262  
 
 263  0
             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
 264  
         } else {
 265  0
             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
 266  
         }
 267  0
         return super.navigate(form, result, request, response);
 268  
     }
 269  
 
 270  
     /**
 271  
      * This method returns the agendaId of the given agenda.  If the agendaId is null a new id will be created.
 272  
      */
 273  
     private String getCreateAgendaId(AgendaBo agenda) {
 274  0
         if (agenda.getId() == null) {
 275  0
             agenda.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_S").toString());
 276  
         }
 277  0
         return agenda.getId();
 278  
     }
 279  
 
 280  
     private void updateRuleAction(AgendaEditor agendaEditor) {
 281  0
         agendaEditor.getAgendaItemLine().getRule().setActions(new ArrayList<ActionBo>());
 282  0
         if (StringUtils.isNotBlank(agendaEditor.getAgendaItemLineRuleAction().getTypeId())) {
 283  0
             updateRuleActionAttributes(agendaEditor.getCustomRuleActionAttributesMap(), agendaEditor.getAgendaItemLineRuleAction());
 284  0
             agendaEditor.getAgendaItemLine().getRule().getActions().add(agendaEditor.getAgendaItemLineRuleAction());
 285  
         }
 286  0
     }
 287  
 
 288  
     private void updateRuleActionAttributes(Map<String, String> customRuleActionAttributeMap, ActionBo action) {
 289  0
         Set<ActionAttributeBo> attributes = new HashSet<ActionAttributeBo>();
 290  
 
 291  0
         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = buildAttributeDefinitionMap(action.getTypeId());
 292  
 
 293  0
         for (Map.Entry<String, String> entry : customRuleActionAttributeMap.entrySet()) {
 294  0
             KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey()); // get the definition from our map
 295  
 
 296  0
             if (attrDef != null) {
 297  0
                 ActionAttributeBo attributeBo = new ActionAttributeBo();
 298  0
                 attributeBo.setActionId(action.getId());
 299  0
                 attributeBo.setAttributeDefinitionId(attrDef.getId());
 300  0
                 attributeBo.setValue(entry.getValue());
 301  0
                 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef));
 302  0
                 attributes.add(attributeBo);
 303  
             }
 304  0
         }
 305  0
         action.setAttributeBos(attributes);
 306  0
     }
 307  
 
 308  
     /**
 309  
      * Build a map from attribute name to attribute definition from all the defined attribute definitions for the
 310  
      * specified rule action type
 311  
      * @param actionTypeId
 312  
      * @return
 313  
      */
 314  
     private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String actionTypeId) {
 315  0
         KrmsAttributeDefinitionService attributeDefinitionService =
 316  
             KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
 317  
 
 318  
         // build a map from attribute name to definition
 319  0
         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
 320  
 
 321  0
         List<KrmsAttributeDefinition> attributeDefinitions =
 322  
                 attributeDefinitionService.findAttributeDefinitionsByType(actionTypeId);
 323  
 
 324  0
         for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
 325  0
             attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
 326  
         }
 327  0
         return attributeDefinitionMap;
 328  
     }
 329  
 
 330  
     /**
 331  
      * This method updates the existing rule in the agenda.
 332  
      */
 333  
     @RequestMapping(params = "methodToCall=" + "editRule")
 334  
     public ModelAndView editRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 335  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 336  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 337  
         // this is the root of the tree:
 338  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 339  0
         AgendaItemBo node = getAgendaItemById(firstItem, getSelectedAgendaItemId(form));
 340  0
         AgendaItemBo agendaItemLine = getAgendaItemLine(form);
 341  
 
 342  0
         updateRuleAction(agendaEditor);
 343  
 
 344  0
         AgendaEditorBusRule rule = new AgendaEditorBusRule();
 345  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 346  0
         MaintenanceDocument document = maintenanceForm.getDocument();
 347  0
         if (rule.processAgendaItemBusinessRules(document)) {
 348  0
             node.setRule(agendaItemLine.getRule());
 349  0
             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
 350  
         } else {
 351  0
             form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
 352  
         }
 353  0
         return super.navigate(form, result, request, response);
 354  
     }
 355  
 
 356  
     /**
 357  
      * @return the ALWAYS {@link AgendaItemInstanceChildAccessor} for the last ALWAYS child of the instance accessed by the parameter.
 358  
      * It will by definition refer to null.  If the instanceAccessor parameter refers to null, then it will be returned.  This is useful
 359  
      * for adding a youngest child to a sibling group.
 360  
      */
 361  
     private AgendaItemInstanceChildAccessor getLastChildsAlwaysAccessor(AgendaItemInstanceChildAccessor instanceAccessor) {
 362  0
         AgendaItemBo next = instanceAccessor.getChild();
 363  0
         if (next == null) return instanceAccessor;
 364  0
         while (next.getAlways() != null) { next = next.getAlways(); };
 365  0
         return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
 366  
     }
 367  
 
 368  
     /**
 369  
      * @return the accessor to the child with the given agendaItemId under the given parent.  This method will search both When TRUE and 
 370  
      * When FALSE sibling groups.  If the instance with the given id is not found, null is returned.
 371  
      * @see AgendaItemChildAccessor for nomenclature explanation
 372  
      */
 373  
     private AgendaItemInstanceChildAccessor getInstanceAccessorToChild(AgendaItemBo parent, String agendaItemId) {
 374  
 
 375  
         // first try When TRUE, then When FALSE via AgendaItemChildAccessor.levelOrderChildren
 376  0
         for (AgendaItemChildAccessor levelOrderChildAccessor : AgendaItemChildAccessor.children) {
 377  
 
 378  0
             AgendaItemBo next = levelOrderChildAccessor.getChild(parent);
 379  
             
 380  
             // if the first item matches, return the accessor from the parent
 381  0
             if (next != null && agendaItemId.equals(next.getId())) return new AgendaItemInstanceChildAccessor(levelOrderChildAccessor, parent);
 382  
 
 383  
             // otherwise walk the children
 384  0
             while (next != null && next.getAlwaysId() != null) {
 385  0
                 if (next.getAlwaysId().equals(agendaItemId)) return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
 386  
                 // move down
 387  0
                 next = next.getAlways();
 388  
             }
 389  
         }
 390  
         
 391  0
         return null;
 392  
     }
 393  
 
 394  
     @RequestMapping(params = "methodToCall=" + "ajaxRefresh")
 395  
     public ModelAndView ajaxRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 396  
             HttpServletRequest request, HttpServletResponse response)
 397  
             throws Exception {
 398  
         // call the super method to avoid the agenda tree being reloaded from the db
 399  0
         return super.updateComponent(form, result, request, response);
 400  
     }
 401  
 
 402  
     @RequestMapping(params = "methodToCall=" + "moveUp")
 403  
     public ModelAndView moveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 404  
             HttpServletRequest request, HttpServletResponse response)
 405  
             throws Exception {
 406  0
         moveSelectedSubtreeUp(form);
 407  
 
 408  0
         return super.refresh(form, result, request, response);
 409  
     }
 410  
 
 411  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveUp")
 412  
     public ModelAndView ajaxMoveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 413  
             HttpServletRequest request, HttpServletResponse response)
 414  
             throws Exception {
 415  0
         moveSelectedSubtreeUp(form);
 416  
 
 417  
         // call the super method to avoid the agenda tree being reloaded from the db
 418  0
         return super.updateComponent(form, result, request, response);
 419  
     }
 420  
 
 421  
     /**
 422  
      *
 423  
      * @param form
 424  
      * @see AgendaItemChildAccessor for nomenclature explanation
 425  
      */
 426  
     private void moveSelectedSubtreeUp(UifFormBase form) {
 427  
 
 428  
         /* Rough algorithm for moving a node up.  This is a "level order" move.  Note that in this tree,
 429  
          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser,
 430  
          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
 431  
          * considered siblings.
 432  
          *
 433  
          * find the following:
 434  
          *   node := the selected node
 435  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 436  
          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
 437  
          *
 438  
          * if (node is first child in sibling group)
 439  
          *     if (node is in When FALSE group)
 440  
          *         move node to last position in When TRUE group
 441  
          *     else
 442  
          *         find youngest child of parentsOlderCousin and put node after it
 443  
          * else
 444  
          *     move node up within its sibling group
 445  
          */
 446  
 
 447  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 448  
         // this is the root of the tree:
 449  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 450  
 
 451  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 452  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 453  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 454  0
         AgendaItemBo parentsOlderCousin = (parent == null) ? null : getNextOldestOfSameGeneration(firstItem, parent);
 455  
 
 456  0
         AgendaItemChildAccessor childAccessor = getOldestChildAccessor(node, parent);
 457  0
         if (childAccessor != null) { // node is first child in sibling group
 458  0
             if (childAccessor == AgendaItemChildAccessor.whenFalse) {
 459  
                 // move node to last position in When TRUE group
 460  0
                 AgendaItemInstanceChildAccessor youngestWhenTrueSiblingInsertionPoint =
 461  
                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenTrue, parent));
 462  0
                 youngestWhenTrueSiblingInsertionPoint.setChild(node);
 463  0
                 AgendaItemChildAccessor.whenFalse.setChild(parent, node.getAlways());
 464  0
                 AgendaItemChildAccessor.always.setChild(node, null);
 465  
 
 466  0
             } else if (parentsOlderCousin != null) {
 467  
                 // find youngest child of parentsOlderCousin and put node after it
 468  0
                 AgendaItemInstanceChildAccessor youngestWhenFalseSiblingInsertionPoint =
 469  
                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, parentsOlderCousin));
 470  0
                 youngestWhenFalseSiblingInsertionPoint.setChild(node);
 471  0
                 AgendaItemChildAccessor.whenTrue.setChild(parent, node.getAlways());
 472  0
                 AgendaItemChildAccessor.always.setChild(node, null);
 473  0
             }
 474  0
         } else if (!selectedItemId.equals(firstItem.getId())) { // conditional to miss special case of first node
 475  
 
 476  0
             AgendaItemBo bogusRootNode = null;
 477  0
             if (parent == null) {
 478  
                 // special case, this is a top level sibling. rig up special parent node
 479  0
                 bogusRootNode = new AgendaItemBo();
 480  0
                 AgendaItemChildAccessor.whenTrue.setChild(bogusRootNode, firstItem);
 481  0
                 parent = bogusRootNode;
 482  
             }
 483  
 
 484  
             // move node up within its sibling group
 485  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 486  0
             AgendaItemBo olderSibling = accessorToSelectedNode.getInstance();
 487  0
             AgendaItemInstanceChildAccessor accessorToOlderSibling = getInstanceAccessorToChild(parent, olderSibling.getId());
 488  
 
 489  0
             accessorToOlderSibling.setChild(node);
 490  0
             accessorToSelectedNode.setChild(node.getAlways());
 491  0
             AgendaItemChildAccessor.always.setChild(node, olderSibling);
 492  
 
 493  0
             if (bogusRootNode != null) {
 494  
                 // clean up special case with bogus root node
 495  0
                 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenTrueId());
 496  
             }
 497  
         }
 498  0
     }
 499  
 
 500  
     @RequestMapping(params = "methodToCall=" + "moveDown")
 501  
     public ModelAndView moveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 502  
             HttpServletRequest request, HttpServletResponse response)
 503  
             throws Exception {
 504  0
         moveSelectedSubtreeDown(form);
 505  
         
 506  0
         return super.refresh(form, result, request, response);
 507  
     }
 508  
 
 509  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveDown")
 510  
     public ModelAndView ajaxMoveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 511  
             HttpServletRequest request, HttpServletResponse response)
 512  
             throws Exception {
 513  0
         moveSelectedSubtreeDown(form);
 514  
 
 515  
         // call the super method to avoid the agenda tree being reloaded from the db
 516  0
         return super.updateComponent(form, result, request, response);
 517  
     }
 518  
 
 519  
     /**
 520  
      *
 521  
      * @param form
 522  
      * @see AgendaItemChildAccessor for nomenclature explanation
 523  
      */
 524  
     private void moveSelectedSubtreeDown(UifFormBase form) {
 525  
 
 526  
         /* Rough algorithm for moving a node down.  This is a "level order" move.  Note that in this tree,
 527  
          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser,
 528  
          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
 529  
          * considered siblings.
 530  
          *
 531  
          * find the following:
 532  
          *   node := the selected node
 533  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 534  
          *   parentsYoungerCousin := the parent's level-order successor (sibling or cousin)
 535  
          *
 536  
          * if (node is last child in sibling group)
 537  
          *     if (node is in When TRUE group)
 538  
          *         move node to first position in When FALSE group
 539  
          *     else
 540  
          *         move to first child of parentsYoungerCousin
 541  
          * else
 542  
          *     move node down within its sibling group
 543  
          */
 544  
 
 545  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 546  
         // this is the root of the tree:
 547  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 548  
 
 549  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 550  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 551  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 552  0
         AgendaItemBo parentsYoungerCousin = (parent == null) ? null : getNextYoungestOfSameGeneration(firstItem, parent);
 553  
 
 554  0
         if (node.getAlways() == null && parent != null) { // node is last child in sibling group
 555  
             // set link to selected node to null
 556  0
             if (parent.getWhenTrue() != null && isSiblings(parent.getWhenTrue(), node)) { // node is in When TRUE group
 557  
                 // move node to first child under When FALSE
 558  
 
 559  0
                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 560  0
                 accessorToSelectedNode.setChild(null);
 561  
 
 562  0
                 AgendaItemBo parentsFirstChild = parent.getWhenFalse();
 563  0
                 AgendaItemChildAccessor.whenFalse.setChild(parent, node);
 564  0
                 AgendaItemChildAccessor.always.setChild(node, parentsFirstChild);
 565  0
             } else if (parentsYoungerCousin != null) { // node is in the When FALSE group
 566  
                 // move to first child of parentsYoungerCousin under When TRUE
 567  
 
 568  0
                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 569  0
                 accessorToSelectedNode.setChild(null);
 570  
 
 571  0
                 AgendaItemBo parentsYoungerCousinsFirstChild = parentsYoungerCousin.getWhenTrue();
 572  0
                 AgendaItemChildAccessor.whenTrue.setChild(parentsYoungerCousin, node);
 573  0
                 AgendaItemChildAccessor.always.setChild(node, parentsYoungerCousinsFirstChild);
 574  0
             }
 575  0
         } else if (node.getAlways() != null) { // move node down within its sibling group
 576  
 
 577  0
             AgendaItemBo bogusRootNode = null;
 578  0
             if (parent == null) {
 579  
                 // special case, this is a top level sibling. rig up special parent node
 580  0
                 bogusRootNode = new AgendaItemBo();
 581  0
                 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
 582  0
                 parent = bogusRootNode;
 583  
             }
 584  
 
 585  
             // move node down within its sibling group
 586  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 587  0
             AgendaItemBo youngerSibling = node.getAlways();
 588  0
             accessorToSelectedNode.setChild(youngerSibling);
 589  0
             AgendaItemChildAccessor.always.setChild(node, youngerSibling.getAlways());
 590  0
             AgendaItemChildAccessor.always.setChild(youngerSibling, node);
 591  
 
 592  0
             if (bogusRootNode != null) {
 593  
                 // clean up special case with bogus root node
 594  0
                 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId());
 595  
             }
 596  
         }
 597  0
     }
 598  
 
 599  
     @RequestMapping(params = "methodToCall=" + "moveLeft")
 600  
     public ModelAndView moveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 601  
             HttpServletRequest request, HttpServletResponse response)
 602  
             throws Exception {
 603  0
         moveSelectedSubtreeLeft(form);
 604  
         
 605  0
         return super.refresh(form, result, request, response);
 606  
     }
 607  
 
 608  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveLeft")
 609  
     public ModelAndView ajaxMoveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 610  
             HttpServletRequest request, HttpServletResponse response)
 611  
             throws Exception {
 612  
 
 613  0
         moveSelectedSubtreeLeft(form);
 614  
 
 615  
         // call the super method to avoid the agenda tree being reloaded from the db
 616  0
         return super.updateComponent(form, result, request, response);
 617  
     }
 618  
 
 619  
     /**
 620  
      *
 621  
      * @param form
 622  
      * @see AgendaItemChildAccessor for nomenclature explanation
 623  
      */
 624  
     private void moveSelectedSubtreeLeft(UifFormBase form) {
 625  
 
 626  
         /*
 627  
          * Move left means make it a younger sibling of it's parent.
 628  
          */
 629  
 
 630  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 631  
         // this is the root of the tree:
 632  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 633  
 
 634  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 635  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 636  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 637  
 
 638  0
         if (parent != null) {
 639  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 640  0
             accessorToSelectedNode.setChild(node.getAlways());
 641  
 
 642  0
             AgendaItemChildAccessor.always.setChild(node, parent.getAlways());
 643  0
             AgendaItemChildAccessor.always.setChild(parent, node);
 644  
         }
 645  0
     }
 646  
 
 647  
 
 648  
     @RequestMapping(params = "methodToCall=" + "moveRight")
 649  
     public ModelAndView moveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 650  
             HttpServletRequest request, HttpServletResponse response)
 651  
             throws Exception {
 652  
 
 653  0
         moveSelectedSubtreeRight(form);
 654  
 
 655  0
         return super.refresh(form, result, request, response);
 656  
     }
 657  
 
 658  
     @RequestMapping(params = "methodToCall=" + "ajaxMoveRight")
 659  
     public ModelAndView ajaxMoveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 660  
             HttpServletRequest request, HttpServletResponse response)
 661  
             throws Exception {
 662  
 
 663  0
         moveSelectedSubtreeRight(form);
 664  
 
 665  
         // call the super method to avoid the agenda tree being reloaded from the db
 666  0
         return super.updateComponent(form, result, request, response);
 667  
     }
 668  
 
 669  
     /**
 670  
      *
 671  
      * @param form
 672  
      * @see AgendaItemChildAccessor for nomenclature explanation
 673  
      */
 674  
     private void moveSelectedSubtreeRight(UifFormBase form) {
 675  
 
 676  
         /*
 677  
          * Move right prefers moving to bottom of upper sibling's When FALSE branch
 678  
          * ... otherwise ..
 679  
          * moves to top of lower sibling's When TRUE branch
 680  
          */
 681  
 
 682  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 683  
         // this is the root of the tree:
 684  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 685  
 
 686  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 687  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 688  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 689  
 
 690  0
         AgendaItemBo bogusRootNode = null;
 691  0
         if (parent == null) {
 692  
             // special case, this is a top level sibling. rig up special parent node
 693  0
             bogusRootNode = new AgendaItemBo();
 694  0
             AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
 695  0
             parent = bogusRootNode;
 696  
         }
 697  
 
 698  0
         AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 699  0
         AgendaItemBo olderSibling = (accessorToSelectedNode.getInstance() == parent) ? null : accessorToSelectedNode.getInstance();
 700  
 
 701  0
         if (olderSibling != null) {
 702  0
             accessorToSelectedNode.setChild(node.getAlways());
 703  0
             AgendaItemInstanceChildAccessor yougestWhenFalseSiblingInsertionPoint =
 704  
                     getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, olderSibling));
 705  0
             yougestWhenFalseSiblingInsertionPoint.setChild(node);
 706  0
             AgendaItemChildAccessor.always.setChild(node, null);
 707  0
         } else if (node.getAlways() != null) { // has younger sibling
 708  0
             accessorToSelectedNode.setChild(node.getAlways());
 709  0
             AgendaItemBo childsWhenTrue = node.getAlways().getWhenTrue();
 710  0
             AgendaItemChildAccessor.whenTrue.setChild(node.getAlways(), node);
 711  0
             AgendaItemChildAccessor.always.setChild(node, childsWhenTrue);
 712  
         }
 713  
 
 714  0
         if (bogusRootNode != null) {
 715  
             // clean up special case with bogus root node
 716  0
             agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId());
 717  
         }
 718  0
     }
 719  
 
 720  
     /**
 721  
      *
 722  
      * @param cousin1
 723  
      * @param cousin2
 724  
      * @return
 725  
      * @see AgendaItemChildAccessor for nomenclature explanation
 726  
      */
 727  
     private boolean isSiblings(AgendaItemBo cousin1, AgendaItemBo cousin2) {
 728  0
         if (cousin1.equals(cousin2)) return true; // this is a bit abusive
 729  
         
 730  
         // can you walk to c1 from ALWAYS links of c2?
 731  0
         AgendaItemBo candidate = cousin2;
 732  0
         while (null != (candidate = candidate.getAlways())) {
 733  0
             if (candidate.equals(cousin1)) return true;
 734  
         }
 735  
         // can you walk to c2 from ALWAYS links of c1?
 736  0
         candidate = cousin1;
 737  0
         while (null != (candidate = candidate.getAlways())) {
 738  0
             if (candidate.equals(cousin2)) return true;
 739  
         }
 740  0
         return false;
 741  
     }
 742  
 
 743  
     /**
 744  
      * This method returns the level order accessor (getWhenTrue or getWhenFalse) that relates the parent directly 
 745  
      * to the child.  If the two nodes don't have such a relationship, null is returned. 
 746  
      * Note that this only finds accessors for oldest children, not younger siblings.
 747  
      * @see AgendaItemChildAccessor for nomenclature explanation
 748  
      */
 749  
     private AgendaItemChildAccessor getOldestChildAccessor(
 750  
             AgendaItemBo child, AgendaItemBo parent) {
 751  0
         AgendaItemChildAccessor levelOrderChildAccessor = null;
 752  
         
 753  0
         if (parent != null) {
 754  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.children) {
 755  0
                 if (child.equals(childAccessor.getChild(parent))) {
 756  0
                     levelOrderChildAccessor = childAccessor;
 757  0
                     break;
 758  
                 }
 759  
             }
 760  
         }
 761  0
         return levelOrderChildAccessor;
 762  
     }
 763  
     
 764  
     /**
 765  
      * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
 766  
      * 
 767  
      * @param agenda
 768  
      * @return
 769  
      */
 770  
     private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
 771  0
         AgendaItemBo firstItem = null;
 772  0
         if (agenda != null && agenda.getItems() != null) for (AgendaItemBo agendaItem : agenda.getItems()) {
 773  0
             if (agenda.getFirstItemId().equals(agendaItem.getId())) {
 774  0
                 firstItem = agendaItem;
 775  0
                 break;
 776  
             }
 777  
         }
 778  0
         return firstItem;
 779  
     }
 780  
     
 781  
     /**
 782  
      * @return the closest younger sibling of the agenda item with the given ID, and if there is no such sibling, the closest younger cousin.
 783  
      * If there is no such cousin either, then null is returned.
 784  
      * @see AgendaItemChildAccessor for nomenclature explanation
 785  
      */
 786  
     private AgendaItemBo getNextYoungestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
 787  
 
 788  0
         int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
 789  0
         List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
 790  0
         buildAgendaItemGenerationList(genList, root, 0, genNumber);
 791  
 
 792  0
         int itemIndex = genList.indexOf(agendaItem);
 793  0
         if (genList.size() > itemIndex + 1) return genList.get(itemIndex + 1);
 794  
 
 795  0
         return null;
 796  
     }
 797  
 
 798  
     /**
 799  
      *
 800  
      * @param currentLevel
 801  
      * @param node
 802  
      * @param agendaItemId
 803  
      * @return
 804  
      * @see AgendaItemChildAccessor for nomenclature explanation
 805  
      */
 806  
     private int getAgendaItemGenerationNumber(int currentLevel, AgendaItemBo node, String agendaItemId) {
 807  0
         int result = -1;
 808  0
         if (agendaItemId.equals(node.getId())) {
 809  0
             result = currentLevel;
 810  
         } else {
 811  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 812  0
                 AgendaItemBo child = childAccessor.getChild(node);
 813  0
                 if (child != null) {
 814  0
                     int nextLevel = currentLevel;
 815  
                     // we don't change the level order parent when we traverse ALWAYS links
 816  0
                     if (childAccessor != AgendaItemChildAccessor.always) {
 817  0
                         nextLevel = currentLevel +1;
 818  
                     }
 819  0
                     result = getAgendaItemGenerationNumber(nextLevel, child, agendaItemId);
 820  0
                     if (result != -1) break;
 821  
                 }
 822  
             }
 823  
         }
 824  0
         return result;
 825  
     }
 826  
 
 827  
     /**
 828  
      *
 829  
      * @param genList
 830  
      * @param node
 831  
      * @param currentLevel
 832  
      * @param generation
 833  
      * @see AgendaItemChildAccessor for nomenclature explanation
 834  
      */
 835  
     private void buildAgendaItemGenerationList(List<AgendaItemBo> genList, AgendaItemBo node, int currentLevel, int generation) {
 836  0
         if (currentLevel == generation) {
 837  0
             genList.add(node);
 838  
         }
 839  
 
 840  0
         if (currentLevel > generation) return;
 841  
 
 842  0
         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 843  0
             AgendaItemBo child = childAccessor.getChild(node);
 844  0
             if (child != null) {
 845  0
                 int nextLevel = currentLevel;
 846  
                 // we don't change the level order parent when we traverse ALWAYS links
 847  0
                 if (childAccessor != AgendaItemChildAccessor.always) {
 848  0
                     nextLevel = currentLevel +1;
 849  
                 }
 850  0
                 buildAgendaItemGenerationList(genList, child, nextLevel, generation);
 851  
             }
 852  
         }
 853  0
     }
 854  
 
 855  
     /**
 856  
      * @return the closest older sibling of the agenda item with the given ID, and if there is no such sibling, the closest older cousin.
 857  
      * If there is no such cousin either, then null is returned.
 858  
      * @see AgendaItemChildAccessor for nomenclature explanation
 859  
      */
 860  
     private AgendaItemBo getNextOldestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
 861  
 
 862  0
         int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
 863  0
         List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
 864  0
         buildAgendaItemGenerationList(genList, root, 0, genNumber);
 865  
 
 866  0
         int itemIndex = genList.indexOf(agendaItem);
 867  0
         if (itemIndex >= 1) return genList.get(itemIndex - 1);
 868  
 
 869  0
         return null;
 870  
     }
 871  
     
 872  
 
 873  
     /**
 874  
      * returns the parent of the item with the passed in id.  Note that {@link AgendaItemBo}s related by ALWAYS relationships are considered siblings.
 875  
      * @see AgendaItemChildAccessor for nomenclature explanation
 876  
      */
 877  
     private AgendaItemBo getParent(AgendaItemBo root, String agendaItemId) {
 878  0
         return getParentHelper(root, null, agendaItemId);
 879  
     }
 880  
 
 881  
     /**
 882  
      *
 883  
      * @param node
 884  
      * @param levelOrderParent
 885  
      * @param agendaItemId
 886  
      * @return
 887  
      * @see AgendaItemChildAccessor for nomenclature explanation
 888  
      */
 889  
     private AgendaItemBo getParentHelper(AgendaItemBo node, AgendaItemBo levelOrderParent, String agendaItemId) {
 890  0
         AgendaItemBo result = null;
 891  0
         if (agendaItemId.equals(node.getId())) {
 892  0
             result = levelOrderParent;
 893  
         } else {
 894  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 895  0
                 AgendaItemBo child = childAccessor.getChild(node);
 896  0
                 if (child != null) {
 897  
                     // we don't change the level order parent when we traverse ALWAYS links 
 898  0
                     AgendaItemBo lop = (childAccessor == AgendaItemChildAccessor.always) ? levelOrderParent : node;
 899  0
                     result = getParentHelper(child, lop, agendaItemId);
 900  0
                     if (result != null) break;
 901  
                 }
 902  
             }
 903  
         }
 904  0
         return result;
 905  
     }
 906  
 
 907  
     /**
 908  
      * Search the tree for the agenda item with the given id.
 909  
      */
 910  
     private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
 911  0
         if (node == null) throw new IllegalArgumentException("node must be non-null");
 912  
 
 913  0
         AgendaItemBo result = null;
 914  
         
 915  0
         if (agendaItemId.equals(node.getId())) {
 916  0
             result = node;
 917  
         } else {
 918  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 919  0
                 AgendaItemBo child = childAccessor.getChild(node);
 920  0
                 if (child != null) {
 921  0
                     result = getAgendaItemById(child, agendaItemId);
 922  0
                     if (result != null) break;
 923  
                 }
 924  
             }
 925  
         } 
 926  0
         return result;
 927  
     }
 928  
 
 929  
     /**
 930  
      * @param form
 931  
      * @return the {@link AgendaEditor} from the form
 932  
      */
 933  
     private AgendaEditor getAgendaEditor(UifFormBase form) {
 934  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 935  0
         return ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 936  
     }
 937  
 
 938  
     private void treeToInOrderList(AgendaItemBo agendaItem, List<AgendaItemBo> listToBuild) {
 939  0
         listToBuild.add(agendaItem);
 940  0
         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 941  0
             AgendaItemBo child = childAccessor.getChild(agendaItem);
 942  0
             if (child != null) treeToInOrderList(child, listToBuild);
 943  
         }
 944  0
     }
 945  
 
 946  
     
 947  
     @RequestMapping(params = "methodToCall=" + "delete")
 948  
     public ModelAndView delete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 949  
             HttpServletRequest request, HttpServletResponse response)
 950  
             throws Exception {
 951  
 
 952  0
         deleteSelectedSubtree(form);
 953  
 
 954  0
         return super.refresh(form, result, request, response);
 955  
     }
 956  
 
 957  
     @RequestMapping(params = "methodToCall=" + "ajaxDelete")
 958  
     public ModelAndView ajaxDelete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 959  
             HttpServletRequest request, HttpServletResponse response)
 960  
             throws Exception {
 961  
 
 962  0
         deleteSelectedSubtree(form);
 963  
 
 964  
         // call the super method to avoid the agenda tree being reloaded from the db
 965  0
         return super.updateComponent(form, result, request, response);
 966  
     }
 967  
 
 968  
     
 969  
     private void deleteSelectedSubtree(UifFormBase form) {
 970  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 971  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 972  0
         String agendaItemSelected = agendaEditor.getSelectedAgendaItemId();
 973  
 
 974  0
         if (firstItem != null) {
 975  
             // need to handle the first item here, our recursive method won't handle it.
 976  0
             if (agendaItemSelected.equals(firstItem.getId())) {
 977  0
                 agendaEditor.getAgenda().setFirstItemId(firstItem.getAlwaysId());
 978  
             } else {
 979  0
                 deleteAgendaItem(firstItem, agendaItemSelected);
 980  
             }
 981  
         }
 982  0
     }
 983  
 
 984  
     private void deleteAgendaItem(AgendaItemBo root, String agendaItemIdToDelete) {
 985  0
         if (deleteAgendaItem(root, AgendaItemChildAccessor.whenTrue, agendaItemIdToDelete) || 
 986  
                 deleteAgendaItem(root, AgendaItemChildAccessor.whenFalse, agendaItemIdToDelete) || 
 987  
                 deleteAgendaItem(root, AgendaItemChildAccessor.always, agendaItemIdToDelete)); // TODO: this is confusing, refactor
 988  0
     }
 989  
     
 990  
     private boolean deleteAgendaItem(AgendaItemBo agendaItem, AgendaItemChildAccessor childAccessor, String agendaItemIdToDelete) {
 991  0
         if (agendaItem == null || childAccessor.getChild(agendaItem) == null) return false;
 992  0
         if (agendaItemIdToDelete.equals(childAccessor.getChild(agendaItem).getId())) {
 993  
             // delete the child in such a way that any ALWAYS children don't get lost from the tree
 994  0
             AgendaItemBo grandchildToKeep = childAccessor.getChild(agendaItem).getAlways();
 995  0
             childAccessor.setChild(agendaItem, grandchildToKeep);
 996  0
             return true;
 997  
         } else {
 998  0
             AgendaItemBo child = childAccessor.getChild(agendaItem);
 999  
             // recurse
 1000  0
             for (AgendaItemChildAccessor nextChildAccessor : AgendaItemChildAccessor.linkedNodes) {
 1001  0
                 if (deleteAgendaItem(child, nextChildAccessor, agendaItemIdToDelete)) return true;
 1002  
             }
 1003  
         }
 1004  0
         return false;
 1005  
     }
 1006  
 
 1007  
     @RequestMapping(params = "methodToCall=" + "ajaxCut")
 1008  
     public ModelAndView ajaxCut(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1009  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 1010  
 
 1011  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1012  
         // this is the root of the tree:
 1013  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 1014  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 1015  
 
 1016  0
         setCutAgendaItemId(form, selectedItemId);
 1017  
 
 1018  
         // call the super method to avoid the agenda tree being reloaded from the db
 1019  0
         return super.updateComponent(form, result, request, response);
 1020  
     }
 1021  
 
 1022  
     @RequestMapping(params = "methodToCall=" + "ajaxPaste")
 1023  
     public ModelAndView ajaxPaste(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1024  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 1025  
 
 1026  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1027  
         // this is the root of the tree:
 1028  0
         AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
 1029  0
         String selectedItemId = agendaEditor.getSelectedAgendaItemId();
 1030  
 
 1031  0
         String agendaItemId = getCutAgendaItemId(form);
 1032  
 
 1033  0
         if (StringUtils.isNotBlank(selectedItemId) && StringUtils.isNotBlank(agendaItemId)) {
 1034  0
             AgendaItemBo node = getAgendaItemById(firstItem, agendaItemId);
 1035  0
             AgendaItemBo orgRefNode = getReferringNode(firstItem, agendaItemId);
 1036  0
             AgendaItemBo newRefNode = getAgendaItemById(firstItem, selectedItemId);
 1037  
 
 1038  0
             if (isSameOrChildNode(node, newRefNode)) {
 1039  
                 // do nothing; can't paste to itself
 1040  
             } else {
 1041  
                 // remove node
 1042  0
                 if (orgRefNode == null) {
 1043  0
                     agendaEditor.getAgenda().setFirstItemId(node.getAlwaysId());
 1044  
                 } else {
 1045  
                     // determine if true, false or always
 1046  
                     // do appropriate operation
 1047  0
                     if (node.getId().equals(orgRefNode.getWhenTrueId())) {
 1048  0
                         orgRefNode.setWhenTrueId(node.getAlwaysId());
 1049  0
                         orgRefNode.setWhenTrue(node.getAlways());
 1050  0
                     } else if(node.getId().equals(orgRefNode.getWhenFalseId())) {
 1051  0
                         orgRefNode.setWhenFalseId(node.getAlwaysId());
 1052  0
                         orgRefNode.setWhenFalse(node.getAlways());
 1053  
                     } else {
 1054  0
                         orgRefNode.setAlwaysId(node.getAlwaysId());
 1055  0
                         orgRefNode.setAlways(node.getAlways());
 1056  
                     }
 1057  
                 }
 1058  
 
 1059  
                 // insert node
 1060  0
                 node.setAlwaysId(newRefNode.getAlwaysId());
 1061  0
                 node.setAlways(newRefNode.getAlways());
 1062  0
                 newRefNode.setAlwaysId(node.getId());
 1063  0
                 newRefNode.setAlways(node);
 1064  
             }
 1065  
         }
 1066  
 
 1067  0
         setCutAgendaItemId(form, null);
 1068  
 
 1069  
         // call the super method to avoid the agenda tree being reloaded from the db
 1070  0
         return super.updateComponent(form, result, request, response);
 1071  
     }
 1072  
 
 1073  
     /**
 1074  
      * This method checks if the node is the same as the new parent node or a when-true/when-fase
 1075  
      * child of the new parent node.
 1076  
      *
 1077  
      * @param node - the node to be checked if it's the same or a child
 1078  
      * @param newParent - the parent node to check against
 1079  
      * @return true if same or child, false otherwise
 1080  
      * @see AgendaItemChildAccessor for nomenclature explanation
 1081  
      */
 1082  
     private boolean isSameOrChildNode(AgendaItemBo node, AgendaItemBo newParent) {
 1083  0
         return isSameOrChildNodeHelper(node, newParent, AgendaItemChildAccessor.children);
 1084  
     }
 1085  
 
 1086  
     private boolean isSameOrChildNodeHelper(AgendaItemBo node, AgendaItemBo newParent, AgendaItemChildAccessor[] childAccessors) {
 1087  0
         boolean result = false;
 1088  0
         if (StringUtils.equals(node.getId(), newParent.getId())) {
 1089  0
             result = true;
 1090  
         } else {
 1091  0
             for (AgendaItemChildAccessor childAccessor : childAccessors) {
 1092  0
                 AgendaItemBo child = childAccessor.getChild(node);
 1093  0
                 if (child != null) {
 1094  0
                     result = isSameOrChildNodeHelper(child, newParent, AgendaItemChildAccessor.linkedNodes);
 1095  0
                     if (result == true) break;
 1096  
                 }
 1097  
             }
 1098  
         }
 1099  0
         return result;
 1100  
     }
 1101  
 
 1102  
     /**
 1103  
      * This method returns the node that points to the specified agendaItemId.
 1104  
      * (returns the next older sibling or the parent if no older sibling exists)
 1105  
      *
 1106  
      * @param root - the first agenda item of the agenda
 1107  
      * @param agendaItemId - agenda item id of the agenda item whose referring node is to be returned
 1108  
      * @return AgendaItemBo that points to the specified agenda item
 1109  
      * @see AgendaItemChildAccessor for nomenclature explanation
 1110  
      */
 1111  
     private AgendaItemBo getReferringNode(AgendaItemBo root, String agendaItemId) {
 1112  0
         return getReferringNodeHelper(root, null, agendaItemId);
 1113  
     }
 1114  
 
 1115  
     private AgendaItemBo getReferringNodeHelper(AgendaItemBo node, AgendaItemBo referringNode, String agendaItemId) {
 1116  0
         AgendaItemBo result = null;
 1117  0
         if (agendaItemId.equals(node.getId())) {
 1118  0
             result = referringNode;
 1119  
         } else {
 1120  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 1121  0
                 AgendaItemBo child = childAccessor.getChild(node);
 1122  0
                 if (child != null) {
 1123  0
                     result = getReferringNodeHelper(child, node, agendaItemId);
 1124  0
                     if (result != null) break;
 1125  
                 }
 1126  
             }
 1127  
         }
 1128  0
         return result;
 1129  
     }
 1130  
 
 1131  
     /**
 1132  
      *  return the sequenceAssessorService
 1133  
      */
 1134  
     private SequenceAccessorService getSequenceAccessorService() {
 1135  0
         if ( sequenceAccessorService == null ) {
 1136  0
             sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
 1137  
         }
 1138  0
         return sequenceAccessorService;
 1139  
     }
 1140  
 
 1141  
     /**
 1142  
      * return the contextBoService
 1143  
      */
 1144  
     private ContextBoService getContextBoService() {
 1145  0
         return KrmsRepositoryServiceLocator.getContextBoService();
 1146  
     }
 1147  
 
 1148  
     /**
 1149  
      * binds a child accessor to an AgendaItemBo instance.  An {@link AgendaItemInstanceChildAccessor} allows you to
 1150  
      * get and set the referent
 1151  
      */
 1152  
     private static class AgendaItemInstanceChildAccessor {
 1153  
         
 1154  
         private final AgendaItemChildAccessor accessor;
 1155  
         private final AgendaItemBo instance;
 1156  
 
 1157  0
         public AgendaItemInstanceChildAccessor(AgendaItemChildAccessor accessor, AgendaItemBo instance) {
 1158  0
             this.accessor = accessor;
 1159  0
             this.instance = instance;
 1160  0
         }
 1161  
         
 1162  
         public void setChild(AgendaItemBo child) {
 1163  0
             accessor.setChild(instance, child);
 1164  0
         }
 1165  
         
 1166  
         public AgendaItemBo getChild() {
 1167  0
             return accessor.getChild(instance);
 1168  
         }
 1169  
         
 1170  0
         public AgendaItemBo getInstance() { return instance; }
 1171  
     }
 1172  
     
 1173  
     /**
 1174  
      * <p>This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations
 1175  
      * require less boiler plate.</p>
 1176  
      *
 1177  
      * <p>The word 'child' in AgendaItemChildAccessor means child in the strict data structures sense, in that the
 1178  
      * instance passed in holds a reference to some other node (or null).  However, when discussing the agenda tree
 1179  
      * and algorithms for manipulating it, the meaning of 'child' is somewhat different, and there are notions of
 1180  
      * 'sibling' and 'cousin' that are tossed about too. It's probably worth explaining that somewhat here:</p>
 1181  
      *
 1182  
      * <p>General principals of relationships when talking about the agenda tree:
 1183  
      * <ul>
 1184  
      * <li>Generation boundaries (parent to child) are across 'When TRUE' and 'When FALSE' references.</li>
 1185  
      * <li>"Age" among siblings & cousins goes from top (oldest) to bottom (youngest).</li>
 1186  
      * <li>siblings are related by 'Always' references.</li>
 1187  
      * </ul>
 1188  
      * </p>
 1189  
      * <p>This diagram of an agenda tree and the following examples seek to illustrate these principals:</p>
 1190  
      * <img src="doc-files/AgendaEditorController-1.png" alt="Example Agenda Items"/>
 1191  
      * <p>Examples:
 1192  
      * <ul>
 1193  
      * <li>A is the parent of B, C, & D</li>
 1194  
      * <li>E is the younger sibling of A</li>
 1195  
      * <li>B is the older cousin of C</li>
 1196  
      * <li>C is the older sibling of D</li>
 1197  
      * <li>F is the younger cousin of D</li>
 1198  
      * </ul>
 1199  
      * </p>
 1200  
      */
 1201  0
     protected static class AgendaItemChildAccessor {
 1202  
         
 1203  0
         private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
 1204  
         
 1205  0
         private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE); 
 1206  0
         private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE); 
 1207  0
         private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS); 
 1208  
 
 1209  
         /**
 1210  
          * Accessors for all linked items
 1211  
          */
 1212  0
         private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
 1213  
         
 1214  
         /**
 1215  
          * Accessors for children (so ALWAYS is omitted);
 1216  
          */
 1217  0
         private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
 1218  
         
 1219  
         private final Child whichChild;
 1220  
         
 1221  0
         private AgendaItemChildAccessor(Child whichChild) {
 1222  0
             if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
 1223  0
             this.whichChild = whichChild;
 1224  0
         }
 1225  
         
 1226  
         /**
 1227  
          * @return the referenced child
 1228  
          */
 1229  
         public AgendaItemBo getChild(AgendaItemBo parent) {
 1230  0
             switch (whichChild) {
 1231  0
             case WHEN_TRUE: return parent.getWhenTrue();
 1232  0
             case WHEN_FALSE: return parent.getWhenFalse();
 1233  0
             case ALWAYS: return parent.getAlways();
 1234  0
             default: throw new IllegalStateException();
 1235  
             }
 1236  
         }
 1237  
         
 1238  
         /**
 1239  
          * Sets the child reference and the child id 
 1240  
          */
 1241  
         public void setChild(AgendaItemBo parent, AgendaItemBo child) {
 1242  0
             switch (whichChild) {
 1243  
             case WHEN_TRUE: 
 1244  0
                 parent.setWhenTrue(child);
 1245  0
                 parent.setWhenTrueId(child == null ? null : child.getId());
 1246  0
                 break;
 1247  
             case WHEN_FALSE:
 1248  0
                 parent.setWhenFalse(child);
 1249  0
                 parent.setWhenFalseId(child == null ? null : child.getId());
 1250  0
                 break;
 1251  
             case ALWAYS:
 1252  0
                 parent.setAlways(child);
 1253  0
                 parent.setAlwaysId(child == null ? null : child.getId());
 1254  0
                 break;
 1255  0
             default: throw new IllegalStateException();
 1256  
             }
 1257  0
         }
 1258  
     }
 1259  
     //
 1260  
     // Rule Editor Controller methods
 1261  
     //
 1262  
     /**
 1263  
      * This method starts an edit proposition.
 1264  
      */
 1265  
     @RequestMapping(params = "methodToCall=" + "goToEditProposition")
 1266  
     public ModelAndView goToEditProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1267  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 1268  
 
 1269  
         // open the selected node for editing
 1270  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1271  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1272  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1273  
 
 1274  
         // find parent
 1275  0
         Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement();
 1276  0
         Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId);
 1277  0
         if (parent != null){
 1278  0
             List<Node<RuleTreeNode,String>> children = parent.getChildren();
 1279  0
             for( int index=0; index< children.size(); index++){
 1280  0
                 Node<RuleTreeNode,String> child = children.get(index);
 1281  0
                 if (propIdMatches(child, selectedPropId)){
 1282  0
                     PropositionBo prop = child.getData().getProposition();
 1283  0
                     boolean editMode =  !prop.getEditMode();
 1284  0
                     prop.setEditMode(editMode);
 1285  
                     // if compound node, set all children into same edit mode
 1286  0
                     if (PropositionType.COMPOUND.getCode().equalsIgnoreCase(prop.getPropositionTypeCode())){
 1287  0
                        for ( PropositionBo compoundComponent : prop.getCompoundComponents() ){
 1288  0
                            compoundComponent.setEditMode(editMode);
 1289  
                        }
 1290  
                     }
 1291  
                     //refresh the tree
 1292  0
                     rule.refreshPropositionTree(null);
 1293  0
                     break;
 1294  
                 }
 1295  
             }
 1296  
         }
 1297  0
         return super.updateComponent(form, result, request, response);
 1298  
     }
 1299  
 
 1300  
     @RequestMapping(params = "methodToCall=" + "addProposition")
 1301  
     public ModelAndView addProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1302  
             HttpServletRequest request, HttpServletResponse response) throws Exception {
 1303  
 
 1304  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1305  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1306  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1307  
 
 1308  
         // find parent
 1309  0
         Node<RuleTreeNode,String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement();
 1310  0
         Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId);
 1311  
 
 1312  
         // add new child at appropriate spot
 1313  0
         if (parent != null){
 1314  0
             List<Node<RuleTreeNode,String>> children = parent.getChildren();
 1315  0
             for( int index=0; index< children.size(); index++){
 1316  0
                 Node<RuleTreeNode,String> child = children.get(index);
 1317  
 
 1318  
                 // if our selected node is a simple proposition, add a new one after
 1319  0
                 if (propIdMatches(child, selectedPropId)){
 1320  
                     // handle special case of adding to a lone simple proposition.
 1321  
                     // in this case, we need to change the root level proposition to a compound proposition
 1322  
                     // move the existing simple proposition as the first compound component,
 1323  
                     // then add a new blank simple prop as the second compound component.
 1324  0
                     if (parent == root &&
 1325  
                         (SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
 1326  
                         SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()))){
 1327  
 
 1328  
                         // create a new compound proposition
 1329  0
                         PropositionBo compound = PropositionBo.createCompoundPropositionBoStub(child.getData().getProposition());
 1330  0
                         compound.setEditMode(true);
 1331  0
                         rule.setProposition(compound);
 1332  0
                         rule.refreshPropositionTree(null);
 1333  0
                     }
 1334  
                     // handle regular case of adding a simple prop to an existing compound prop
 1335  0
                     else if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
 1336  
                        SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType())){
 1337  
 
 1338  
                         // build new Blank Proposition
 1339  0
                         PropositionBo blank = PropositionBo.createSimplePropositionBoStub(child.getData().getProposition(),PropositionType.SIMPLE.getCode());
 1340  
                         //add it to the parent
 1341  0
                         PropositionBo parentProp = parent.getData().getProposition();
 1342  0
                         parentProp.getCompoundComponents().add(((index/2)+1), blank);
 1343  
 
 1344  0
                         rule.refreshPropositionTree(true);
 1345  0
                     }
 1346  
 
 1347  
                     break;
 1348  
                 }
 1349  
             }
 1350  0
         } else {
 1351  
             // special case, if root has no children, add a new simple proposition
 1352  
             // todo: how to add compound proposition. - just add another to the firs simple
 1353  0
             if (root.getChildren().isEmpty()){
 1354  0
                 PropositionBo blank = PropositionBo.createSimplePropositionBoStub(null,PropositionType.SIMPLE.getCode());
 1355  0
                 blank.setRuleId(rule.getId());
 1356  0
                 blank.setTypeId(rule.getTypeId());  // ?? bug
 1357  0
                 rule.setPropId(blank.getId());
 1358  0
                 rule.setProposition(blank);
 1359  0
                 rule.refreshPropositionTree(true);
 1360  
             }
 1361  
         }
 1362  0
         return super.updateComponent(form, result, request, response);
 1363  
     }
 1364  
 
 1365  
     /**
 1366  
      *
 1367  
      * This method adds an opCode Node to separate components in a compound proposition.
 1368  
      *
 1369  
      * @param currentNode
 1370  
      * @param prop
 1371  
      * @return
 1372  
      */
 1373  
     private void addOpCodeNode(Node currentNode, PropositionBo prop, int index){
 1374  0
         String opCodeLabel = "";
 1375  
 
 1376  0
         if (LogicalOperator.AND.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){
 1377  0
             opCodeLabel = "AND";
 1378  0
         } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){
 1379  0
             opCodeLabel = "OR";
 1380  
         }
 1381  0
         Node<RuleTreeNode, String> aNode = new Node<RuleTreeNode, String>();
 1382  0
         aNode.setNodeLabel("");
 1383  0
         aNode.setNodeType("ruleTreeNode compoundOpCodeNode");
 1384  0
         aNode.setData(new CompoundOpCodeNode(prop));
 1385  0
         currentNode.insertChildAt(index, aNode);
 1386  0
     }
 1387  
 
 1388  
 
 1389  
     private boolean propIdMatches(Node<RuleTreeNode, String> node, String propId){
 1390  0
         if (propId!=null && node != null && node.getData() != null && propId.equalsIgnoreCase(node.getData().getProposition().getId())) {
 1391  0
             return true;
 1392  
         }
 1393  0
         return false;
 1394  
     }
 1395  
 
 1396  
     private Node<RuleTreeNode, String> findPropositionTreeNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){
 1397  0
         Node<RuleTreeNode,String> bingo = null;
 1398  0
         if (currentNode.getData() != null){
 1399  0
             RuleTreeNode dataNode = currentNode.getData();
 1400  0
             if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId())){
 1401  0
                 return currentNode;
 1402  
             }
 1403  
         }
 1404  0
         List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
 1405  0
         for( Node<RuleTreeNode,String> child : children){
 1406  0
               bingo = findPropositionTreeNode(child, selectedPropId);
 1407  0
               if (bingo != null) break;
 1408  
         }
 1409  0
         return bingo;
 1410  
     }
 1411  
 
 1412  
     private Node<RuleTreeNode, String> findParentPropositionNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){
 1413  0
         Node<RuleTreeNode,String> bingo = null;
 1414  0
         if (selectedPropId != null) {
 1415  
             // if it's in children, we have the parent
 1416  0
             List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
 1417  0
             for( Node<RuleTreeNode,String> child : children){
 1418  0
                 RuleTreeNode dataNode = child.getData();
 1419  0
                 if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId()))
 1420  0
                     return currentNode;
 1421  0
             }
 1422  
 
 1423  
             // if not found check grandchildren
 1424  0
             for( Node<RuleTreeNode,String> kid : children){
 1425  0
                   bingo = findParentPropositionNode(kid, selectedPropId);
 1426  0
                   if (bingo != null) break;
 1427  
             }
 1428  
         }
 1429  0
         return bingo;
 1430  
     }
 1431  
 
 1432  
     /**
 1433  
      * This method return the index of the position of the child that matches the id
 1434  
      * @param parent
 1435  
      * @param propId
 1436  
      * @return index if found, -1 if not found
 1437  
      */
 1438  
     private int findChildIndex(Node<RuleTreeNode,String> parent, String propId){
 1439  
         int index;
 1440  0
         List<Node<RuleTreeNode,String>> children = parent.getChildren();
 1441  0
         for(index=0; index< children.size(); index++){
 1442  0
             Node<RuleTreeNode,String> child = children.get(index);
 1443  
             // if our selected node is a simple proposition, add a new one after
 1444  0
             if (propIdMatches(child, propId)){
 1445  0
                 return index;
 1446  
             }
 1447  
         }
 1448  0
         return -1;
 1449  
     }
 1450  
 
 1451  
     @RequestMapping(params = "methodToCall=" + "movePropositionUp")
 1452  
     public ModelAndView movePropositionUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1453  
             HttpServletRequest request, HttpServletResponse response)
 1454  
             throws Exception {
 1455  0
         moveSelectedProposition(form, true);
 1456  
 
 1457  0
         return super.updateComponent(form, result, request, response);
 1458  
     }
 1459  
 
 1460  
     @RequestMapping(params = "methodToCall=" + "movePropositionDown")
 1461  
     public ModelAndView movePropositionDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1462  
             HttpServletRequest request, HttpServletResponse response)
 1463  
             throws Exception {
 1464  0
         moveSelectedProposition(form, false);
 1465  
 
 1466  0
         return super.updateComponent(form, result, request, response);
 1467  
     }
 1468  
 
 1469  
     private void moveSelectedProposition(UifFormBase form, boolean up) {
 1470  
 
 1471  
         /* Rough algorithm for moving a node up.
 1472  
          *
 1473  
          * find the following:
 1474  
          *   node := the selected node
 1475  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 1476  
          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
 1477  
          *
 1478  
          */
 1479  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1480  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1481  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1482  
 
 1483  
         // find parent
 1484  0
         Node<RuleTreeNode,String> parent = findParentPropositionNode(rule.getPropositionTree().getRootElement(), selectedPropId);
 1485  
 
 1486  
         // add new child at appropriate spot
 1487  0
         if (parent != null){
 1488  0
             List<Node<RuleTreeNode,String>> children = parent.getChildren();
 1489  0
             for( int index=0; index< children.size(); index++){
 1490  0
                 Node<RuleTreeNode,String> child = children.get(index);
 1491  
                 // if our selected node is a simple proposition, add a new one after
 1492  0
                 if (propIdMatches(child, selectedPropId)){
 1493  0
                     if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
 1494  
                        SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
 1495  
                        RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ){
 1496  
 
 1497  0
                         if (((index > 0) && up) || ((index <(children.size() - 1)&& !up))){
 1498  
                             //remove it from its current spot
 1499  0
                             PropositionBo parentProp = parent.getData().getProposition();
 1500  0
                             PropositionBo workingProp = parentProp.getCompoundComponents().remove(index/2);
 1501  0
                             if (up){
 1502  0
                                 parentProp.getCompoundComponents().add((index/2)-1, workingProp);
 1503  
                             }else{
 1504  0
                                 parentProp.getCompoundComponents().add((index/2)+1, workingProp);
 1505  
                             }
 1506  
 
 1507  
                             // insert it in the new spot
 1508  
                             // redisplay the tree (editMode = true)
 1509  0
                             boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()));
 1510  0
                             rule.refreshPropositionTree(editMode);
 1511  0
                         }
 1512  
                     }
 1513  
 
 1514  
                     break;
 1515  
                 }
 1516  
             }
 1517  
         }
 1518  0
     }
 1519  
     @RequestMapping(params = "methodToCall=" + "movePropositionLeft")
 1520  
     public ModelAndView movePropositionLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1521  
             HttpServletRequest request, HttpServletResponse response)
 1522  
             throws Exception {
 1523  
 
 1524  
         /* Rough algorithm for moving a node up.
 1525  
          *
 1526  
          * find the following:
 1527  
          *   node := the selected node
 1528  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 1529  
          *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
 1530  
          *
 1531  
          */
 1532  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1533  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1534  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1535  
 
 1536  
         // find agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement()parent
 1537  0
         Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement();
 1538  0
         Node<RuleTreeNode,String> parent = findParentPropositionNode(root, selectedPropId);
 1539  0
         if ((parent != null) && (RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(parent.getNodeType()))){
 1540  0
             Node<RuleTreeNode,String> granny = findParentPropositionNode(root,parent.getData().getProposition().getId());
 1541  0
             if (granny != root){
 1542  0
                 int oldIndex = findChildIndex(parent, selectedPropId);
 1543  0
                 int newIndex = findChildIndex(granny, parent.getData().getProposition().getId());
 1544  0
                 if (oldIndex >= 0 && newIndex >= 0){
 1545  0
                     PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(oldIndex/2);
 1546  0
                     granny.getData().getProposition().getCompoundComponents().add((newIndex/2)+1, prop);
 1547  0
                     rule.refreshPropositionTree(false);
 1548  
                 }
 1549  
             } else {
 1550  
                 // TODO: do we allow moving up to the root?
 1551  
                 // we could add a new top level compound node, with current root as 1st child,
 1552  
                 // and move the node to the second child.
 1553  
             }
 1554  
         }
 1555  0
         return super.updateComponent(form, result, request, response);
 1556  
     }
 1557  
 
 1558  
     @RequestMapping(params = "methodToCall=" + "movePropositionRight")
 1559  
     public ModelAndView movePropositionRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1560  
             HttpServletRequest request, HttpServletResponse response)
 1561  
             throws Exception {
 1562  
         /* Rough algorithm for moving a node Right
 1563  
          * if the selected node is above a compound proposition, move it into the compound proposition as the first child
 1564  
          * if the node is above a simple proposition, do nothing.
 1565  
          * find the following:
 1566  
          *   node := the selected node
 1567  
          *   parent := the selected node's parent, its containing node
 1568  
          *   nextSibling := the node after the selected node
 1569  
          *
 1570  
          */
 1571  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1572  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1573  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1574  
 
 1575  
         // find parent
 1576  0
         Node<RuleTreeNode,String> parent = findParentPropositionNode(
 1577  
                 rule.getPropositionTree().getRootElement(), selectedPropId);
 1578  0
         if (parent != null){
 1579  0
             int index = findChildIndex(parent, selectedPropId);
 1580  
             // if we are the last child, do nothing, otherwise
 1581  0
             if (index >= 0 && index+1 < parent.getChildren().size()){
 1582  0
                 Node<RuleTreeNode,String> child = parent.getChildren().get(index);
 1583  0
                 Node<RuleTreeNode,String> nextSibling = parent.getChildren().get(index+2);
 1584  
                 // if selected node above a compound node, move it into it as first child
 1585  0
                 if(RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(nextSibling.getNodeType()) ){
 1586  
                     // remove selected node from it's current spot
 1587  0
                     PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(index/2);
 1588  
                     // add it to it's siblings children
 1589  0
                     nextSibling.getData().getProposition().getCompoundComponents().add(0, prop);
 1590  0
                     rule.refreshPropositionTree(false);
 1591  
                 }
 1592  
             }
 1593  
         }
 1594  0
         return super.updateComponent(form, result, request, response);
 1595  
     }
 1596  
 
 1597  
 
 1598  
     @RequestMapping(params = "methodToCall=" + "cutProposition")
 1599  
     public ModelAndView cutProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1600  
             HttpServletRequest request, HttpServletResponse response)
 1601  
             throws Exception {
 1602  
 
 1603  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1604  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1605  0
         agendaEditor.setCutPropositionId(selectedPropId);
 1606  
 
 1607  0
         return super.updateComponent(form, result, request, response);
 1608  
     }
 1609  
 
 1610  
     @RequestMapping(params = "methodToCall=" + "pasteProposition")
 1611  
     public ModelAndView pasteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1612  
             HttpServletRequest request, HttpServletResponse response)
 1613  
             throws Exception {
 1614  
 
 1615  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1616  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1617  
 
 1618  
         // get selected id
 1619  0
         String cutPropId = agendaEditor.getCutPropositionId();
 1620  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1621  
 
 1622  0
         if (selectedPropId == cutPropId) {
 1623  
                 // do nothing; can't paste to itself
 1624  
         } else {
 1625  
 
 1626  
             // proposition tree root
 1627  0
             Node<RuleTreeNode, String> root = rule.getPropositionTree().getRootElement();
 1628  
 
 1629  0
             if (StringUtils.isNotBlank(selectedPropId) && StringUtils.isNotBlank(cutPropId)) {
 1630  0
                 Node<RuleTreeNode,String> parentNode = findParentPropositionNode(root, selectedPropId);
 1631  
                 PropositionBo newParent;
 1632  0
                 if (parentNode == root){
 1633  
                     // special case
 1634  
                     // build new top level compound proposition,
 1635  
                     // add existing as first child
 1636  
                     // then paste cut node as 2nd child
 1637  0
                     newParent = PropositionBo.createCompoundPropositionBoStub2(
 1638  
                             root.getChildren().get(0).getData().getProposition());
 1639  0
                     newParent.setEditMode(true);
 1640  0
                     rule.setProposition(newParent);
 1641  
                 } else {
 1642  0
                     newParent = parentNode.getData().getProposition();
 1643  
                 }
 1644  0
                 PropositionBo oldParent = findParentPropositionNode(root, cutPropId).getData().getProposition();
 1645  
 
 1646  0
                 PropositionBo workingProp = null;
 1647  
                 // cut from old
 1648  0
                 if (oldParent != null){
 1649  0
                     List <PropositionBo> children = oldParent.getCompoundComponents();
 1650  0
                     for( int index=0; index< children.size(); index++){
 1651  0
                         if (cutPropId.equalsIgnoreCase(children.get(index).getId())){
 1652  0
                             workingProp = oldParent.getCompoundComponents().remove(index);
 1653  0
                             break;
 1654  
                         }
 1655  
                     }
 1656  
                 }
 1657  
 
 1658  
                 // add to new
 1659  0
                 if (newParent != null && workingProp != null){
 1660  0
                     List <PropositionBo> children = newParent.getCompoundComponents();
 1661  0
                     for( int index=0; index< children.size(); index++){
 1662  0
                         if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){
 1663  0
                             children.add(index+1, workingProp);
 1664  0
                             break;
 1665  
                         }
 1666  
                     }
 1667  
                 }
 1668  
                 // TODO: determine edit mode.
 1669  
 //                boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()));
 1670  0
                 rule.refreshPropositionTree(false);
 1671  
             }
 1672  
         }
 1673  0
         agendaEditor.setCutPropositionId(null);
 1674  
         // call the super method to avoid the agenda tree being reloaded from the db
 1675  0
         return super.updateComponent(form, result, request, response);
 1676  
     }
 1677  
 
 1678  
     @RequestMapping(params = "methodToCall=" + "deleteProposition")
 1679  
     public ModelAndView deleteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1680  
             HttpServletRequest request, HttpServletResponse response)
 1681  
             throws Exception {
 1682  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1683  0
         String selectedPropId = agendaEditor.getSelectedPropositionId();
 1684  0
         Node<RuleTreeNode, String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement();
 1685  
 
 1686  0
         PropositionBo parent = findParentPropositionNode(root, selectedPropId).getData().getProposition();
 1687  0
         if (parent != null){
 1688  0
             List <PropositionBo> children = parent.getCompoundComponents();
 1689  0
             for( int index=0; index< children.size(); index++){
 1690  0
                 if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){
 1691  0
                     parent.getCompoundComponents().remove(index);
 1692  0
                     break;
 1693  
                 }
 1694  
             }
 1695  
         }
 1696  
         //TODO: handle edit mode
 1697  0
         agendaEditor.getAgendaItemLine().getRule().refreshPropositionTree(false);
 1698  0
         return super.updateComponent(form, result, request, response);
 1699  
     }
 1700  
 
 1701  
     @RequestMapping(params = "methodToCall=" + "updateCompoundOperator")
 1702  
     public ModelAndView updateCompoundOperator(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 1703  
             HttpServletRequest request, HttpServletResponse response)
 1704  
             throws Exception {
 1705  
 
 1706  0
         AgendaEditor agendaEditor = getAgendaEditor(form);
 1707  0
         RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
 1708  0
         rule.refreshPropositionTree(false);
 1709  
 
 1710  0
         return super.updateComponent(form, result, request, response);
 1711  
     }
 1712  
 }