001/** 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krms.util; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.util.tree.Node; 020import org.kuali.rice.krms.api.repository.LogicalOperator; 021import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType; 022import org.kuali.rice.krms.api.repository.proposition.PropositionType; 023import org.kuali.rice.krms.dto.PropositionEditor; 024import org.kuali.rice.krms.dto.PropositionParameterEditor; 025import org.kuali.rice.krms.dto.RuleEditor; 026import org.kuali.rice.krms.tree.node.RuleEditorTreeNode; 027 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031 032/** 033 * Utility class with common functionality on the proposition tree. 034 * 035 * @author Kuali Student Team 036 */ 037public class PropositionTreeUtil { 038 039 public static final String DOC_NEW_DATAOBJECT_PATH = "document.newMaintainableObject.dataObject"; 040 public static final String EDIT_TREE_NEW_COLLECTION_LINE = DOC_NEW_DATAOBJECT_PATH + ".editTree"; 041 042 public static String getBindingPath(PropositionEditor proposition, String propertyName) { 043 return getBindingPrefix(proposition) + "proposition." + propertyName; 044 } 045 046 public static String getBindingPrefix(PropositionEditor proposition) { 047 return DOC_NEW_DATAOBJECT_PATH + "." + proposition.getBindingPath() + "."; 048 } 049 050 public static Node<RuleEditorTreeNode, String> findParentPropositionNode(Node<RuleEditorTreeNode, String> currentNode, String selectedPropKey) { 051 Node<RuleEditorTreeNode, String> bingo = null; 052 if (selectedPropKey != null) { 053 // if it's in children, we have the parent 054 List<Node<RuleEditorTreeNode, String>> children = currentNode.getChildren(); 055 for (Node<RuleEditorTreeNode, String> child : children) { 056 RuleEditorTreeNode dataNode = child.getData(); 057 if (selectedPropKey.equalsIgnoreCase(dataNode.getProposition().getKey())) { 058 return currentNode; 059 } 060 } 061 062 // if not found check grandchildren 063 for (Node<RuleEditorTreeNode, String> kid : children) { 064 bingo = findParentPropositionNode(kid, selectedPropKey); 065 if (bingo != null) { 066 break; 067 } 068 } 069 } 070 return bingo; 071 } 072 073 /** 074 * @return the {@link org.kuali.rice.krms.impl.repository.PropositionBo} from the form 075 */ 076 public static PropositionEditor getProposition(RuleEditor ruleEditor) { 077 078 if (ruleEditor != null) { 079 String selectedPropKey = ruleEditor.getSelectedKey(); 080 return findProposition(ruleEditor.getEditTree().getRootElement(), selectedPropKey); 081 } 082 083 return null; 084 } 085 086 public static PropositionEditor findProposition(Node<RuleEditorTreeNode, String> currentNode, String selectedPropKey) { 087 088 if ((selectedPropKey == null) || (selectedPropKey.isEmpty())) { 089 return null; 090 } 091 092 // if it's in children, we have the parent 093 for (Node<RuleEditorTreeNode, String> child : currentNode.getChildren()) { 094 PropositionEditor proposition = child.getData().getProposition(); 095 if (selectedPropKey.equalsIgnoreCase(proposition.getKey())) { 096 return proposition; 097 } else if ("S".equals(proposition.getPropositionTypeCode()) && proposition.isEditMode()) { 098 return proposition; 099 } else if (!proposition.isEditMode()) { 100 // if not found check grandchildren 101 proposition = findProposition(child, selectedPropKey); 102 if (proposition != null) { 103 return proposition; 104 } 105 } 106 } 107 108 return null; 109 } 110 111 /** 112 * Find and return the node containing the proposition that is in currently in edit mode 113 * 114 * @param node the node to start searching from (typically the root) 115 * @return the node that is currently being edited, if any. Otherwise, null. 116 */ 117 public static Node<RuleEditorTreeNode, String> findEditedProposition(Node<RuleEditorTreeNode, String> node) { 118 Node<RuleEditorTreeNode, String> result = null; 119 if (node.getData() != null && node.getData().getProposition() != null && node.getData().getProposition() 120 .isEditMode()) { 121 result = node; 122 } else { 123 for (Node<RuleEditorTreeNode, String> child : node.getChildren()) { 124 result = findEditedProposition(child); 125 if (result != null) { 126 break; 127 } 128 } 129 } 130 return result; 131 } 132 133 /** 134 * disable edit mode for all Nodes beneath and including the passed in Node 135 * 136 * @param proposition 137 */ 138 public static boolean resetEditModeOnPropositionTree(PropositionEditor proposition) { 139 if(proposition==null){ 140 return false; 141 } 142 143 boolean editMode = proposition.isEditMode(); 144 proposition.setEditMode(false); 145 if (proposition.getCompoundEditors() != null) { 146 for (PropositionEditor childProp : proposition.getCompoundEditors()) { 147 if(resetEditModeOnPropositionTree(childProp)) { 148 editMode = true; 149 } 150 } 151 } 152 return editMode; 153 } 154 155 /** 156 * Builds a logical string expression from the proposition tree. 157 * 158 * @param proposition 159 * @return 160 */ 161 public static String configureLogicExpression(PropositionEditor proposition) { 162 // If the prop is a compound proposition, calls itself for each of the compoundComponents 163 if (PropositionType.COMPOUND.getCode().equalsIgnoreCase(proposition.getPropositionTypeCode())) { 164 StringBuilder logicExpression = new StringBuilder(); 165 boolean first = true; 166 for (PropositionEditor child : proposition.getCompoundEditors()) { 167 // add an opcode node in between each of the children. 168 if (!first) { 169 logicExpression.append(" " + getLabelForOperator(proposition.getCompoundOpCode()) + " "); 170 } 171 first = false; 172 // call to build the childs node 173 String compoundExpression = configureLogicExpression(child); 174 if (compoundExpression.length() > 1) { 175 logicExpression.append("(" + compoundExpression + ")"); 176 } else { 177 logicExpression.append(compoundExpression); 178 } 179 } 180 return logicExpression.toString(); 181 } else { 182 return proposition.getKey(); 183 } 184 } 185 186 /** 187 * This method creates a partially populated Simple PropositionBo with 188 * three parameters: a term type paramter (value not assigned) 189 * a operation parameter 190 * a constant parameter (value set to empty string) 191 * The returned PropositionBo has an generatedId. The type code and ruleId properties are assigned the 192 * same value as the sibling param passed in. 193 * Each PropositionParameter has the id generated, and type, sequenceNumber, 194 * propId default values set. The value is set to "". 195 * 196 * @param sibling - 197 * @return a PropositionBo partially populated. 198 */ 199 public static PropositionEditor createSimplePropositionBoStub(PropositionEditor sibling, Class<? extends PropositionEditor> propClass) throws IllegalAccessException, InstantiationException { 200 // create a simple proposition Bo 201 PropositionEditor prop = propClass.newInstance(); 202 prop.setPropositionTypeCode(PropositionType.SIMPLE.getCode()); 203 prop.setNewProp(true); 204 prop.setEditMode(true); 205 if (sibling != null) { 206 prop.setRuleId(sibling.getRuleId()); 207 } 208 209 prop.setParameters(createParameterList()); 210 211 return prop; 212 } 213 214 public static List<PropositionParameterEditor> createParameterList() { 215 // create blank proposition parameters 216 PropositionParameterEditor pTerm = new PropositionParameterEditor(PropositionParameterType.TERM.getCode(), Integer.valueOf("0")); 217 PropositionParameterEditor pOp = new PropositionParameterEditor(PropositionParameterType.OPERATOR.getCode(), Integer.valueOf("2")); 218 PropositionParameterEditor pConst = new PropositionParameterEditor(PropositionParameterType.CONSTANT.getCode(), Integer.valueOf("1")); 219 220 return Arrays.asList(pTerm, pConst, pOp); 221 } 222 223 public static PropositionEditor createCompoundPropositionBoStub(PropositionEditor existing, boolean addNewChild, Class<? extends PropositionEditor> propClass) throws InstantiationException, IllegalAccessException { 224 // create a simple proposition Bo 225 PropositionEditor prop = createCompoundPropositionBoStub(existing, propClass); 226 227 if (addNewChild) { 228 PropositionEditor newProp = createSimplePropositionBoStub(existing, propClass); 229 prop.getCompoundEditors().add(newProp); 230 prop.setEditMode(false); // set the parent edit mode back to null or we end up with 2 props in edit mode 231 } 232 233 return prop; 234 } 235 236 public static PropositionEditor createCompoundPropositionBoStub(PropositionEditor existing, Class<? extends PropositionEditor> propClass) throws IllegalAccessException, InstantiationException { 237 // create a simple proposition Bo 238 PropositionEditor prop = propClass.newInstance(); 239 prop.setNewProp(true); 240 prop.setPropositionTypeCode(PropositionType.COMPOUND.getCode()); 241 prop.setRuleId(existing.getRuleId()); 242 prop.setEditMode(false); 243 244 List<PropositionEditor> components = new ArrayList<PropositionEditor>(); 245 components.add(existing); 246 prop.setCompoundEditors(components); 247 return prop; 248 } 249 250 public static void cancelNewProp(PropositionEditor proposition) { 251 int i = 0; 252 if (proposition.getCompoundEditors() != null) { 253 while (i < proposition.getCompoundEditors().size()) { 254 PropositionEditor child = proposition.getCompoundEditors().get(i); 255 if (child.isNewProp() && child.isEditMode()) { 256 proposition.getCompoundEditors().remove(child); 257 continue; 258 } else { 259 cancelNewProp(child); 260 } 261 i++; 262 } 263 } 264 } 265 266 /** 267 * This method walks through the proposition tree including the root node on the tree 268 * and remove all parent compound propositions that only have one child. 269 * 270 * @param rule 271 */ 272 public static void removeCompoundProp(RuleEditor rule) { 273 //Check if the root only has one child, if so set the child as the root proposition. 274 PropositionEditor root = rule.getPropositionEditor(); 275 if (root.getCompoundEditors() != null) { 276 if(root.getCompoundEditors().size() == 1) { 277 rule.setProposition(root.getCompoundEditors().get(0)); 278 } 279 280 //Remove single parent from proposition tree. 281 removeCompoundProp(rule.getPropositionEditor()); 282 } 283 284 } 285 286 /** 287 * This method walks through the proposition tree and remove all parent compound propositions 288 * that only have one child. 289 * 290 * Note: This does not handle the root as the root need to be set on the rule. 291 * 292 * @param proposition 293 */ 294 public static void removeCompoundProp(PropositionEditor proposition){ 295 if (proposition.getCompoundEditors() != null) { 296 297 // Handle the scenario if the inpust proposition only have one child. 298 if(proposition.getCompoundEditors().size()==1){ 299 PropositionEditor child = proposition.getCompoundEditors().get(0); 300 if(PropositionType.COMPOUND.getCode().equalsIgnoreCase(child.getPropositionTypeCode())) { 301 proposition.setCompoundEditors(child.getCompoundEditors()); 302 } 303 } 304 305 // Now handle the children. 306 for(int i=proposition.getCompoundEditors().size()-1;i>=0;i--){ 307 PropositionEditor child = proposition.getCompoundEditors().get(i); 308 if(PropositionType.COMPOUND.getCode().equalsIgnoreCase(child.getPropositionTypeCode())) { 309 if (child.getCompoundEditors().isEmpty()) { 310 proposition.getCompoundEditors().remove(i); 311 } else { 312 if (child.getCompoundEditors().size() == 1) { 313 proposition.getCompoundEditors().set(i, child.getCompoundEditors().get(0)); 314 } 315 removeCompoundProp(child); 316 } 317 } 318 } 319 } 320 } 321 322 public static void resetNewProp(PropositionEditor proposition) { 323 proposition.setNewProp(false); 324 if (proposition.getCompoundEditors() != null) { 325 for (PropositionEditor child : proposition.getCompoundEditors()) { 326 resetNewProp(child); 327 } 328 } 329 } 330 331 /** 332 * Returns the first parameter of type term in the list. 333 * 334 * @param parameters 335 * @return 336 */ 337 public static PropositionParameterEditor getTermParameter(List<PropositionParameterEditor> parameters) { 338 return getParameterForType(parameters, PropositionParameterType.TERM); 339 } 340 341 /** 342 * Returns the first parameter of type constant in the list. 343 * 344 * @param parameters 345 * @return 346 */ 347 public static PropositionParameterEditor getConstantParameter(List<PropositionParameterEditor> parameters) { 348 return getParameterForType(parameters, PropositionParameterType.CONSTANT); 349 } 350 351 /** 352 * Returns the first parameter of type operator in the list. 353 * 354 * @param parameters 355 * @return 356 */ 357 public static PropositionParameterEditor getOperatorParameter(List<PropositionParameterEditor> parameters) { 358 return getParameterForType(parameters, PropositionParameterType.OPERATOR); 359 } 360 361 /** 362 * Returns the first parameter of the given type in the list. 363 * 364 * @param parameters 365 * @param type 366 * @return 367 */ 368 public static PropositionParameterEditor getParameterForType(List<PropositionParameterEditor> parameters, PropositionParameterType type) { 369 370 if (parameters == null) { 371 return null; 372 } 373 374 for (PropositionParameterEditor parameter : parameters) { 375 if (type.getCode().equals(parameter.getParameterType())) { 376 return parameter; 377 } 378 } 379 return null; 380 } 381 382 /** 383 * Returns a label based on the operator code. 384 * 385 * @param opCode 386 * @return 387 */ 388 public static String getLabelForOperator(String opCode) { 389 if (LogicalOperator.AND.getCode().equalsIgnoreCase(opCode)) { 390 return "AND"; 391 } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(opCode)) { 392 return "OR"; 393 } 394 return StringUtils.EMPTY; 395 } 396 397 public static boolean isSimpleCompounds(PropositionEditor propositionEditor) { 398 for (int index = 0; index < propositionEditor.getCompoundEditors().size(); index++) { 399 if (propositionEditor.getCompoundEditors().get(index).getPropositionTypeCode().equals("C")) { 400 return false; 401 } 402 } 403 return true; 404 } 405}