Coverage Report - org.kuali.rice.krms.impl.ui.AgendaEditorController
 
Classes in this File Line Coverage Branch Coverage Complexity
AgendaEditorController
0%
0/240
0%
0/164
4.484
AgendaEditorController$1
0%
0/1
N/A
4.484
AgendaEditorController$AgendaItemChildAccessor
0%
0/27
0%
0/16
4.484
AgendaEditorController$AgendaItemChildAccessor$Child
0%
0/1
N/A
4.484
AgendaEditorController$AgendaItemInstanceChildAccessor
0%
0/8
N/A
4.484
 
 1  
 /*
 2  
  * Copyright 2007 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 1.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/ecl1.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 java.util.ArrayList;
 19  
 import java.util.List;
 20  
 
 21  
 import javax.servlet.http.HttpServletRequest;
 22  
 import javax.servlet.http.HttpServletResponse;
 23  
 
 24  
 import org.apache.commons.collections.MapUtils;
 25  
 import org.kuali.rice.krad.service.BusinessObjectService;
 26  
 import org.kuali.rice.krad.service.KRADServiceLocator;
 27  
 import org.kuali.rice.krad.web.controller.MaintenanceDocumentController;
 28  
 import org.kuali.rice.krad.web.form.MaintenanceForm;
 29  
 import org.kuali.rice.krad.web.form.UifFormBase;
 30  
 import org.kuali.rice.krms.impl.repository.AgendaBo;
 31  
 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
 32  
 import org.kuali.rice.krms.impl.repository.ContextBo;
 33  
 import org.springframework.stereotype.Controller;
 34  
 import org.springframework.validation.BindingResult;
 35  
 import org.springframework.web.bind.annotation.ModelAttribute;
 36  
 import org.springframework.web.bind.annotation.RequestMapping;
 37  
 import org.springframework.web.servlet.ModelAndView;
 38  
 
 39  
 /**
 40  
  * Controller for the Test UI Page
 41  
  * 
 42  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 43  
  */
 44  0
 @Controller
 45  
 @RequestMapping(value = "/krmsAgendaEditor")
 46  0
 public class AgendaEditorController extends MaintenanceDocumentController {
 47  
 
 48  
     @Override
 49  
     public MaintenanceForm createInitialForm(HttpServletRequest request) {
 50  0
         return new MaintenanceForm();
 51  
     }
 52  
     
 53  
     /**
 54  
      * This overridden method does extra work on refresh to populate the context and agenda
 55  
      * 
 56  
      * @see org.kuali.rice.krad.web.spring.controller.UifControllerBase#refresh(org.kuali.rice.krad.web.spring.form.UifFormBase, org.springframework.validation.BindingResult, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 57  
      */
 58  
     @RequestMapping(params = "methodToCall=" + "refresh")
 59  
     @Override
 60  
     public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 61  
             HttpServletRequest request, HttpServletResponse response)
 62  
             throws Exception {
 63  
         
 64  0
         MapUtils.verbosePrint(System.out, "actionParameters", form.getActionParameters());
 65  0
         MapUtils.verbosePrint(System.out, "requestParameters", request.getParameterMap());
 66  
         
 67  0
         String agendaId = null;
 68  
 
 69  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 70  0
         String conversionFields = maintenanceForm.getActionParameters().get("conversionFields");
 71  0
         String refreshCaller = request.getParameter("refreshCaller");
 72  
 
 73  
         // handle return from agenda lookup
 74  
         // TODO: this condition is sloppy 
 75  0
         if (refreshCaller != null && refreshCaller.contains("Agenda") && refreshCaller.contains("LookupView") &&
 76  
                 conversionFields != null &&
 77  
                 // TODO: this is sloppy
 78  
                 conversionFields.contains("agenda.id")) {
 79  0
             AgendaEditor editorDocument =
 80  
                     ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
 81  0
             agendaId = editorDocument.getAgenda().getId();
 82  0
             AgendaBo agenda = getBoService().findBySinglePrimaryKey(AgendaBo.class, agendaId);
 83  0
             editorDocument.setAgenda(agenda);
 84  
 
 85  0
             if (agenda.getContextId() != null) {
 86  0
                 ContextBo context = getBoService().findBySinglePrimaryKey(ContextBo.class, agenda.getContextId());
 87  0
                 editorDocument.setContext(context);
 88  
             }
 89  
         }
 90  
         
 91  0
         return super.refresh(form, result, request, response);
 92  
     }
 93  
     
 94  
     /**
 95  
      * @return the ALWAYS {@link AgendaItemInstanceChildAccessor} for the last ALWAYS child of the instance accessed by the parameter.
 96  
      * It will by definition refer to null.  If the instanceAccessor parameter refers to null, then it will be returned.  This is useful
 97  
      * for adding a youngest child to a sibling group. 
 98  
      */
 99  
     private AgendaItemInstanceChildAccessor getLastChildsAlwaysAccessor(AgendaItemInstanceChildAccessor instanceAccessor) {
 100  0
         AgendaItemBo next = instanceAccessor.getChild();
 101  0
         if (next == null) return instanceAccessor;
 102  0
         while (next.getAlways() != null) { next = next.getAlways(); };
 103  0
         return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
 104  
     }
 105  
 
 106  
     /**
 107  
      * @return the accessor to the child with the given agendaItemId under the given parent.  This method will search both When TRUE and 
 108  
      * When FALSE sibling groups.  If the instance with the given id is not found, null is returned.
 109  
      */
 110  
     private AgendaItemInstanceChildAccessor getInstanceAccessorToChild(AgendaItemBo parent, String agendaItemId) {
 111  
 
 112  
         // first try When TRUE, then When FALSE via AgendaItemChildAccessor.levelOrderChildren
 113  0
         for (AgendaItemChildAccessor levelOrderChildAccessor : AgendaItemChildAccessor.children) {
 114  
 
 115  0
             AgendaItemBo next = levelOrderChildAccessor.getChild(parent);
 116  
             
 117  
             // if the first item matches, return the accessor from the parent
 118  0
             if (next != null && agendaItemId.equals(next.getId())) return new AgendaItemInstanceChildAccessor(levelOrderChildAccessor, parent);
 119  
 
 120  
             // otherwise walk the children
 121  0
             while (next != null && next.getAlwaysId() != null) {
 122  0
                 if (next.getAlwaysId().equals(agendaItemId)) return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
 123  
                 // move down
 124  0
                 next = next.getAlways();
 125  
             }
 126  
         }
 127  
         
 128  0
         return null;
 129  
     }
 130  
     
 131  
     @RequestMapping(params = "methodToCall=" + "moveUp")
 132  
     public ModelAndView moveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 133  
             HttpServletRequest request, HttpServletResponse response)
 134  
             throws Exception {
 135  
 
 136  
        /* Rough algorithm for moving a node up.  This is a "level order" move.  Note that in this tree,
 137  
         * level order means something a bit funky.  We are defining a level as it would be displayed in the browser, 
 138  
         * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
 139  
         * considered siblings.
 140  
         *
 141  
         * find the following: 
 142  
         *   node := the selected node
 143  
         *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 144  
         *   parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
 145  
         *
 146  
         * if (node is first child in sibling group)
 147  
         *     if (node is in When FALSE group) 
 148  
         *         move node to last position in When TRUE group
 149  
         *     else 
 150  
         *         find youngest child of parentsOlderCousin and put node after it
 151  
         * else 
 152  
         *     move node up within its sibling group
 153  
         */
 154  
         
 155  0
         AgendaBo agenda = getAgenda(form, request);
 156  
         // this is the root of the tree:
 157  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 158  
 
 159  0
         String selectedItemId = request.getParameter("agenda_item_selected");
 160  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 161  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 162  0
         AgendaItemBo parentsOlderCousin = (parent == null) ? null : getNextOldestOfSameGeneration(firstItem, parent.getId());
 163  
 
 164  0
         AgendaItemChildAccessor childAccessor = getOldestChildAccessor(node, parent);
 165  0
         if (childAccessor != null) { // node is first child in sibling group
 166  0
             if (childAccessor == AgendaItemChildAccessor.whenFalse) {
 167  
                 // move node to last position in When TRUE group
 168  0
                 AgendaItemInstanceChildAccessor youngestWhenTrueSiblingInsertionPoint = 
 169  
                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenTrue, parent));
 170  0
                 youngestWhenTrueSiblingInsertionPoint.setChild(node);
 171  0
                 AgendaItemChildAccessor.whenFalse.setChild(parent, node.getAlways());
 172  0
                 AgendaItemChildAccessor.always.setChild(node, null);
 173  
                 
 174  0
             } else if (parentsOlderCousin != null) {
 175  
                 // find youngest child of parentsOlderCousin and put node after it
 176  0
                 AgendaItemInstanceChildAccessor youngestWhenFalseSiblingInsertionPoint = 
 177  
                         getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, parentsOlderCousin));
 178  0
                 youngestWhenFalseSiblingInsertionPoint.setChild(node);
 179  0
                 AgendaItemChildAccessor.whenTrue.setChild(parent, node.getAlways());
 180  0
                 AgendaItemChildAccessor.always.setChild(node, null);
 181  0
             }
 182  0
         } else if (!selectedItemId.equals(firstItem.getId())) { // conditional to miss special case of first node
 183  
             
 184  0
             AgendaItemBo bogusRootNode = null;
 185  0
             if (parent == null) {
 186  
                 // special case, this is a top level sibling. rig up special parent node
 187  0
                 bogusRootNode = new AgendaItemBo();
 188  0
                 AgendaItemChildAccessor.whenTrue.setChild(bogusRootNode, firstItem);
 189  0
                 parent = bogusRootNode;
 190  
             } 
 191  
             
 192  
             // move node up within its sibling group
 193  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 194  0
             AgendaItemBo olderSibling = accessorToSelectedNode.getInstance();
 195  0
             AgendaItemInstanceChildAccessor accessorToOlderSibling = getInstanceAccessorToChild(parent, olderSibling.getId());
 196  
 
 197  0
             accessorToOlderSibling.setChild(node);
 198  0
             accessorToSelectedNode.setChild(node.getAlways());
 199  0
             AgendaItemChildAccessor.always.setChild(node, olderSibling);
 200  
             
 201  0
             if (bogusRootNode != null) {
 202  
                 // clean up special case with bogus root node
 203  0
                 agenda.setFirstItemId(bogusRootNode.getWhenTrueId());
 204  
             }
 205  
         }
 206  
         
 207  0
         return super.refresh(form, result, request, response);
 208  
     }
 209  
     
 210  
     @RequestMapping(params = "methodToCall=" + "moveDown")
 211  
     public ModelAndView moveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 212  
             HttpServletRequest request, HttpServletResponse response)
 213  
             throws Exception {
 214  
 
 215  
         /* Rough algorithm for moving a node down.  This is a "level order" move.  Note that in this tree,
 216  
          * level order means something a bit funky.  We are defining a level as it would be displayed in the browser, 
 217  
          * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
 218  
          * considered siblings.
 219  
          *
 220  
          * find the following: 
 221  
          *   node := the selected node
 222  
          *   parent := the selected node's parent, its containing node (via when true or when false relationship)
 223  
          *   parentsYoungerCousin := the parent's level-order successor (sibling or cousin)
 224  
          *
 225  
          * if (node is last child in sibling group)
 226  
          *     if (node is in When TRUE group) 
 227  
          *         move node to first position in When FALSE group
 228  
          *     else 
 229  
          *         move to first child of parentsYoungerCousin
 230  
          * else 
 231  
          *     move node down within its sibling group
 232  
          */
 233  
         
 234  
 
 235  0
         AgendaBo agenda = getAgenda(form, request);
 236  
         // this is the root of the tree:
 237  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 238  
 
 239  0
         String selectedItemId = request.getParameter("agenda_item_selected");
 240  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 241  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 242  0
         AgendaItemBo parentsYoungerCousin = (parent == null) ? null : getNextYoungestOfSameGeneration(firstItem, parent.getId());
 243  
 
 244  0
         if (node.getAlways() == null) { // node is last child in sibling group
 245  
             // set link to selected node to null
 246  0
             if (parent.getWhenTrue() != null && isSiblings(parent.getWhenTrue(), node)) { // node is in When TRUE group
 247  
                 // move node to first child under When FALSE
 248  
                 
 249  0
                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 250  0
                 accessorToSelectedNode.setChild(null);
 251  
 
 252  0
                 AgendaItemBo parentsFirstChild = parent.getWhenFalse();
 253  0
                 AgendaItemChildAccessor.whenFalse.setChild(parent, node);
 254  0
                 AgendaItemChildAccessor.always.setChild(node, parentsFirstChild);
 255  0
             } else if (parentsYoungerCousin != null) { // node is in the When FALSE group
 256  
                 // move to first child of parentsYoungerCousin under When TRUE
 257  
                 
 258  0
                 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 259  0
                 accessorToSelectedNode.setChild(null);
 260  
 
 261  0
                 AgendaItemBo parentsYoungerCousinsFirstChild = parentsYoungerCousin.getWhenTrue();
 262  0
                 AgendaItemChildAccessor.whenTrue.setChild(parent, node);
 263  0
                 AgendaItemChildAccessor.always.setChild(node, parentsYoungerCousinsFirstChild);
 264  0
             }
 265  
         } else { // move node down within its sibling group
 266  
             
 267  0
             AgendaItemBo bogusRootNode = null;
 268  0
             if (parent == null) {
 269  
                 // special case, this is a top level sibling. rig up special parent node
 270  0
                 bogusRootNode = new AgendaItemBo();
 271  0
                 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
 272  0
                 parent = bogusRootNode;
 273  
             } 
 274  
             
 275  
             // move node down within its sibling group
 276  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 277  0
             AgendaItemBo youngerSibling = node.getAlways();
 278  0
             accessorToSelectedNode.setChild(youngerSibling);
 279  0
             AgendaItemChildAccessor.always.setChild(node, youngerSibling.getAlways());
 280  0
             AgendaItemChildAccessor.always.setChild(youngerSibling, node);
 281  
             
 282  0
             if (bogusRootNode != null) {
 283  
                 // clean up special case with bogus root node
 284  0
                 agenda.setFirstItemId(bogusRootNode.getWhenFalseId());
 285  
             }
 286  
         }
 287  
         
 288  0
         return super.refresh(form, result, request, response);
 289  
     }
 290  
 
 291  
     @RequestMapping(params = "methodToCall=" + "moveLeft")
 292  
     public ModelAndView moveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 293  
             HttpServletRequest request, HttpServletResponse response)
 294  
             throws Exception {
 295  
 
 296  
         /* 
 297  
          * Move left means make it a younger sibling of it's parent.
 298  
          */
 299  
         
 300  
 
 301  0
         AgendaBo agenda = getAgenda(form, request);
 302  
         // this is the root of the tree:
 303  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 304  
 
 305  0
         String selectedItemId = request.getParameter("agenda_item_selected");
 306  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 307  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 308  
 
 309  0
         if (parent != null) {
 310  0
             AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 311  0
             accessorToSelectedNode.setChild(node.getAlways());
 312  
             
 313  0
             AgendaItemChildAccessor.always.setChild(node, parent.getAlways());
 314  0
             AgendaItemChildAccessor.always.setChild(parent, node);
 315  
         }
 316  
         
 317  0
         return super.refresh(form, result, request, response);
 318  
     }
 319  
     
 320  
     @RequestMapping(params = "methodToCall=" + "moveRight")
 321  
     public ModelAndView moveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 322  
             HttpServletRequest request, HttpServletResponse response)
 323  
             throws Exception {
 324  
 
 325  
         /* 
 326  
          * Move right prefers moving to bottom of upper sibling's When FALSE branch
 327  
          * ... otherwise ..
 328  
          * moves to top of lower sibling's When TRUE branch
 329  
          */
 330  
 
 331  0
         AgendaBo agenda = getAgenda(form, request);
 332  
         // this is the root of the tree:
 333  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 334  
 
 335  0
         String selectedItemId = request.getParameter("agenda_item_selected");
 336  0
         AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
 337  0
         AgendaItemBo parent = getParent(firstItem, selectedItemId);
 338  
 
 339  0
         AgendaItemBo bogusRootNode = null;
 340  0
         if (parent == null) {
 341  
             // special case, this is a top level sibling. rig up special parent node
 342  0
             bogusRootNode = new AgendaItemBo();
 343  0
             AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
 344  0
             parent = bogusRootNode;
 345  
         } 
 346  
 
 347  0
         AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
 348  0
         AgendaItemBo olderSibling = (accessorToSelectedNode.getInstance() == parent) ? null : accessorToSelectedNode.getInstance();
 349  
 
 350  0
         if (olderSibling != null) {
 351  0
             accessorToSelectedNode.setChild(node.getAlways());
 352  0
             AgendaItemInstanceChildAccessor yougestWhenFalseSiblingInsertionPoint = 
 353  
                     getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, olderSibling));
 354  0
             yougestWhenFalseSiblingInsertionPoint.setChild(node);
 355  0
             AgendaItemChildAccessor.always.setChild(node, null);
 356  0
         } else if (node.getAlways() != null) { // has younger sibling
 357  0
             accessorToSelectedNode.setChild(node.getAlways());
 358  0
             AgendaItemBo childsWhenTrue = node.getAlways().getWhenTrue();
 359  0
             AgendaItemChildAccessor.whenTrue.setChild(node.getAlways(), node);
 360  0
             AgendaItemChildAccessor.always.setChild(node, childsWhenTrue);
 361  
         }
 362  
         
 363  0
         if (bogusRootNode != null) {
 364  
             // clean up special case with bogus root node
 365  0
             agenda.setFirstItemId(bogusRootNode.getWhenFalseId());
 366  
         }
 367  
         
 368  0
         return super.refresh(form, result, request, response);
 369  
     }
 370  
 
 371  
     
 372  
     
 373  
     private boolean isSiblings(AgendaItemBo cousin1, AgendaItemBo cousin2) {
 374  0
         if (cousin1.equals(cousin2)) return true; // this is a bit abusive
 375  
         
 376  
         // can you walk to c1 from ALWAYS links of c2?
 377  0
         AgendaItemBo candidate = cousin2;
 378  0
         while (null != (candidate = candidate.getAlways())) {
 379  0
             if (candidate.equals(cousin1)) return true;
 380  
         }
 381  
         // can you walk to c2 from ALWAYS links of c1?
 382  0
         candidate = cousin1;
 383  0
         while (null != (candidate = candidate.getAlways())) {
 384  0
             if (candidate.equals(cousin2)) return true;
 385  
         }
 386  0
         return false;
 387  
     }
 388  
 
 389  
     /**
 390  
      * This method returns the level order accessor (getWhenTrue or getWhenFalse) that relates the parent directly 
 391  
      * to the child.  If the two nodes don't have such a relationship, null is returned. 
 392  
      * Note that this only finds accessors for oldest children, not younger siblings.
 393  
      */
 394  
     private AgendaItemChildAccessor getOldestChildAccessor(
 395  
             AgendaItemBo child, AgendaItemBo parent) {
 396  0
         AgendaItemChildAccessor levelOrderChildAccessor = null;
 397  
         
 398  0
         if (parent != null) {
 399  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.children) {
 400  0
                 if (child.equals(childAccessor.getChild(parent))) {
 401  0
                     levelOrderChildAccessor = childAccessor;
 402  0
                     break;
 403  
                 }
 404  
             }
 405  
         }
 406  0
         return levelOrderChildAccessor;
 407  
     }
 408  
     
 409  
     /**
 410  
      * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
 411  
      * 
 412  
      * @param agenda
 413  
      * @return
 414  
      */
 415  
     private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
 416  0
         AgendaItemBo firstItem = null;
 417  0
         for (AgendaItemBo agendaItem : agenda.getItems()) {
 418  0
             if (agenda.getFirstItemId().equals(agendaItem.getId())) {
 419  0
                 firstItem = agendaItem;
 420  0
                 break;
 421  
             }
 422  
         }
 423  0
         return firstItem;
 424  
     }
 425  
     
 426  
     /**
 427  
      * @return the closest younger sibling of the agenda item with the given ID, and if there is no such sibling, the closest younger cousin.
 428  
      * If there is no such cousin either, then null is returned.
 429  
      */
 430  
     private AgendaItemBo getNextYoungestOfSameGeneration(AgendaItemBo root, String agendaItemId) {
 431  0
         AgendaItemBo result = getNextYoungestOfSameGenerationHelper(root, 0, new ArrayList<AgendaItemBo>(), agendaItemId);
 432  0
         if (result == NULL_AGENDA_ITEM) result = null;
 433  0
         return result;
 434  
     }
 435  
     
 436  
     private AgendaItemBo getNextYoungestOfSameGenerationHelper(AgendaItemBo node, int level, ArrayList<AgendaItemBo> youngerCousinByLevel, String agendaItemId) {
 437  0
         AgendaItemBo result = null;
 438  0
         if (agendaItemId.equals(node.getId())) {
 439  0
             result = (youngerCousinByLevel.size() > level) ? youngerCousinByLevel.get(level) : NULL_AGENDA_ITEM;
 440  
         } else {
 441  0
             for (int i=AgendaItemChildAccessor.linkedNodes.length; i >= 0; --i) {
 442  0
                 AgendaItemChildAccessor childAccessor = AgendaItemChildAccessor.linkedNodes[i];
 443  0
                 AgendaItemBo child = childAccessor.getChild(node);
 444  0
                 if (child != null) {
 445  0
                     int levelNext = level;
 446  
                     // we don't change the level order parent when we traverse ALWAYS links
 447  
                     // and we only adjust the olderCousinByLevel when we traverse ALWAYS links
 448  0
                     if (childAccessor == AgendaItemChildAccessor.always) {
 449  
                         // ensure that we can set the element at index=level
 450  0
                         while (youngerCousinByLevel.size() <= level) youngerCousinByLevel.add(null);
 451  0
                         youngerCousinByLevel.set(level, node);
 452  
                     } else {
 453  0
                         levelNext = level +1;
 454  
                     }
 455  0
                     result = getNextYoungestOfSameGenerationHelper(child, levelNext, youngerCousinByLevel, agendaItemId);
 456  0
                     if (result != null) break;
 457  
                 }
 458  
             }
 459  
         }
 460  0
         return result;
 461  
     }
 462  
     
 463  
     /**
 464  
      * @return the closest older sibling of the agenda item with the given ID, and if there is no such sibling, the closest older cousin.
 465  
      * If there is no such cousin either, then null is returned.
 466  
      */
 467  
     private AgendaItemBo getNextOldestOfSameGeneration(AgendaItemBo root, String agendaItemId) {
 468  0
         AgendaItemBo result = getNextOldestOfSameGenerationHelper(root, 0, new ArrayList<AgendaItemBo>(), agendaItemId);
 469  0
         if (result == NULL_AGENDA_ITEM) result = null;
 470  0
         return result;
 471  
     }
 472  
     
 473  
     // null object used for detecting when there is no predecessor without walking the whole tree needlessly.
 474  0
     private static final AgendaItemBo NULL_AGENDA_ITEM = new AgendaItemBo();
 475  
     
 476  
     private AgendaItemBo getNextOldestOfSameGenerationHelper(AgendaItemBo node, int level, ArrayList<AgendaItemBo> olderCousinByLevel, String agendaItemId) {
 477  0
         AgendaItemBo result = null;
 478  0
         if (agendaItemId.equals(node.getId())) {
 479  0
             result = (olderCousinByLevel.size() > level) ? olderCousinByLevel.get(level) : NULL_AGENDA_ITEM;
 480  
         } else {
 481  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 482  0
                 AgendaItemBo child = childAccessor.getChild(node);
 483  0
                 if (child != null) {
 484  0
                     int levelNext = level;
 485  
                     // we don't change the level order parent when we traverse ALWAYS links
 486  
                     // and we only adjust the olderCousinByLevel when we traverse ALWAYS links
 487  0
                     if (childAccessor == AgendaItemChildAccessor.always) {
 488  
                         // ensure that we can set the element at index=level
 489  0
                         while (olderCousinByLevel.size() <= level) olderCousinByLevel.add(null);
 490  0
                         olderCousinByLevel.set(level, node);
 491  
                     } else {
 492  0
                         levelNext = level +1;
 493  
                     }
 494  0
                     result = getNextOldestOfSameGenerationHelper(child, levelNext, olderCousinByLevel, agendaItemId);
 495  0
                     if (result != null) break;
 496  
                 }
 497  
             }
 498  
         }
 499  0
         return result;
 500  
     }
 501  
 
 502  
     /**
 503  
      * returns the parent of the item with the passed in id.  Note that {@link AgendaItemBo}s related by ALWAYS relationships are considered siblings.
 504  
      */ 
 505  
     private AgendaItemBo getParent(AgendaItemBo root, String agendaItemId) {
 506  0
         return getParentHelper(root, null, agendaItemId);
 507  
     }
 508  
     
 509  
     private AgendaItemBo getParentHelper(AgendaItemBo node, AgendaItemBo levelOrderParent, String agendaItemId) {
 510  0
         AgendaItemBo result = null;
 511  0
         if (agendaItemId.equals(node.getId())) {
 512  0
             result = levelOrderParent;
 513  
         } else {
 514  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 515  0
                 AgendaItemBo child = childAccessor.getChild(node);
 516  0
                 if (child != null) {
 517  
                     // we don't change the level order parent when we traverse ALWAYS links 
 518  0
                     AgendaItemBo lop = (childAccessor == AgendaItemChildAccessor.always) ? levelOrderParent : node;
 519  0
                     result = getParentHelper(child, lop, agendaItemId);
 520  0
                     if (result != null) break;
 521  
                 }
 522  
             }
 523  
         }
 524  0
         return result;
 525  
     }
 526  
 
 527  
     /**
 528  
      * Search the tree for the agenda item with the given id.
 529  
      */
 530  
     private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
 531  0
         if (node == null) throw new IllegalArgumentException("node must be non-null");
 532  
 
 533  0
         AgendaItemBo result = null;
 534  
         
 535  0
         if (agendaItemId.equals(node.getId())) {
 536  0
             result = node;
 537  
         } else {
 538  0
             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 539  0
                 AgendaItemBo child = childAccessor.getChild(node);
 540  0
                 if (child != null) {
 541  0
                     result = getAgendaItemById(child, agendaItemId);
 542  0
                     if (result != null) break;
 543  
                 }
 544  
             }
 545  
         } 
 546  0
         return result;
 547  
     }
 548  
 
 549  
     /**
 550  
      * This method gets the agenda from the form
 551  
      * 
 552  
      * @param form
 553  
      * @param request
 554  
      * @return
 555  
      */
 556  
     private AgendaBo getAgenda(UifFormBase form, HttpServletRequest request) {
 557  0
         MaintenanceForm maintenanceForm = (MaintenanceForm) form;
 558  0
         AgendaEditor editorDocument = ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
 559  0
         AgendaBo agenda = editorDocument.getAgenda();
 560  0
         return agenda;
 561  
     }
 562  
     
 563  
     private void treeToInOrderList(AgendaItemBo agendaItem, List<AgendaItemBo> listToBuild) {
 564  0
         listToBuild.add(agendaItem);
 565  0
         for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
 566  0
             AgendaItemBo child = childAccessor.getChild(agendaItem);
 567  0
             if (child != null) treeToInOrderList(child, listToBuild);
 568  
         }
 569  0
     }
 570  
 
 571  
     
 572  
     @RequestMapping(params = "methodToCall=" + "delete")
 573  
     public ModelAndView delete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
 574  
             HttpServletRequest request, HttpServletResponse response)
 575  
             throws Exception {
 576  
         
 577  0
         AgendaBo agenda = getAgenda(form, request);
 578  0
         AgendaItemBo firstItem = getFirstAgendaItem(agenda);
 579  
 
 580  0
         String agendaItemSelected = request.getParameter("agenda_item_selected");
 581  
         
 582  0
         if (firstItem != null) {
 583  
             // need to handle the first item here, our recursive method won't handle it.  
 584  0
             if (agendaItemSelected.equals(firstItem.getAgendaId())) {
 585  0
                 agenda.setFirstItemId(firstItem.getAlwaysId());
 586  
             } else {
 587  0
                 deleteAgendaItem(firstItem, agendaItemSelected);
 588  
             }
 589  
         }
 590  
         
 591  0
         return super.refresh(form, result, request, response);
 592  
     }
 593  
 
 594  
     // TODO: smarter delete would be desirable.
 595  
     private void deleteAgendaItem(AgendaItemBo root, String agendaItemIdToDelete) {
 596  0
         if (deleteAgendaItem(root, AgendaItemChildAccessor.whenTrue, agendaItemIdToDelete) || 
 597  
                 deleteAgendaItem(root, AgendaItemChildAccessor.whenFalse, agendaItemIdToDelete) || 
 598  
                 deleteAgendaItem(root, AgendaItemChildAccessor.always, agendaItemIdToDelete)); // TODO: this is confusing, refactor
 599  0
     }
 600  
     
 601  
     private boolean deleteAgendaItem(AgendaItemBo agendaItem, AgendaItemChildAccessor childAccessor, String agendaItemIdToDelete) {
 602  0
         if (agendaItem == null || childAccessor.getChild(agendaItem) == null) return false;
 603  0
         if (agendaItemIdToDelete.equals(childAccessor.getChild(agendaItem).getId())) {
 604  
             // delete the child in such a way that any ALWAYS children don't get lost from the tree
 605  0
             AgendaItemBo grandchildToKeep = childAccessor.getChild(agendaItem).getAlways();
 606  0
             childAccessor.setChild(agendaItem, grandchildToKeep);
 607  0
             return true;
 608  
         } else {
 609  0
             AgendaItemBo child = childAccessor.getChild(agendaItem);
 610  
             // recurse
 611  0
             for (AgendaItemChildAccessor nextChildAccessor : AgendaItemChildAccessor.linkedNodes) {
 612  0
                 if (deleteAgendaItem(child, nextChildAccessor, agendaItemIdToDelete)) return true;
 613  
             }
 614  
         }
 615  0
         return false;
 616  
     }
 617  
 
 618  
 
 619  
     private BusinessObjectService getBoService() {
 620  0
         return KRADServiceLocator.getBusinessObjectService();
 621  
     }
 622  
 
 623  
     /**
 624  
      * binds a child accessor to an AgendaItemBo instance
 625  
      */
 626  
     private static class AgendaItemInstanceChildAccessor {
 627  
         
 628  
         private final AgendaItemChildAccessor accessor;
 629  
         private final AgendaItemBo instance;
 630  
 
 631  0
         public AgendaItemInstanceChildAccessor(AgendaItemChildAccessor accessor, AgendaItemBo instance) {
 632  0
             this.accessor = accessor;
 633  0
             this.instance = instance;
 634  0
         }
 635  
         
 636  
         public void setChild(AgendaItemBo child) {
 637  0
             accessor.setChild(instance, child);
 638  0
         }
 639  
         
 640  
         public AgendaItemBo getChild() {
 641  0
             return accessor.getChild(instance);
 642  
         }
 643  
         
 644  0
         public AgendaItemBo getInstance() { return instance; }
 645  
     }
 646  
     
 647  
     /**
 648  
      * This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations 
 649  
      * require less boiler plate 
 650  
      */
 651  0
     private static class AgendaItemChildAccessor {
 652  
         
 653  0
         private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
 654  
         
 655  0
         private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE); 
 656  0
         private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE); 
 657  0
         private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS); 
 658  
         
 659  
         /**
 660  
          * Accessors for all linked items
 661  
          */
 662  0
         private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
 663  
         
 664  
         /**
 665  
          * Accessors for children (so ALWAYS is omitted);
 666  
          */
 667  0
         private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
 668  
         
 669  
         private final Child whichChild;
 670  
         
 671  0
         private AgendaItemChildAccessor(Child whichChild) {
 672  0
             if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
 673  0
             this.whichChild = whichChild;
 674  0
         }
 675  
         
 676  
         /**
 677  
          * @return the referenced child
 678  
          */
 679  
         public AgendaItemBo getChild(AgendaItemBo parent) {
 680  0
             switch (whichChild) {
 681  0
             case WHEN_TRUE: return parent.getWhenTrue();
 682  0
             case WHEN_FALSE: return parent.getWhenFalse();
 683  0
             case ALWAYS: return parent.getAlways();
 684  0
             default: throw new IllegalStateException();
 685  
             }
 686  
         }
 687  
         
 688  
         /**
 689  
          * Sets the child reference and the child id 
 690  
          */
 691  
         public void setChild(AgendaItemBo parent, AgendaItemBo child) {
 692  0
             switch (whichChild) {
 693  
             case WHEN_TRUE: 
 694  0
                 parent.setWhenTrue(child);
 695  0
                 parent.setWhenTrueId(child == null ? null : child.getId());
 696  0
                 break;
 697  
             case WHEN_FALSE:
 698  0
                 parent.setWhenFalse(child);
 699  0
                 parent.setWhenFalseId(child == null ? null : child.getId());
 700  0
                 break;
 701  
             case ALWAYS:
 702  0
                 parent.setAlways(child);
 703  0
                 parent.setAlwaysId(child == null ? null : child.getId());
 704  0
                 break;
 705  0
             default: throw new IllegalStateException();
 706  
             }
 707  0
         }
 708  
     }
 709  
 
 710  
 }