View Javadoc

1   /**
2    * Copyright 2005-2013 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.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.util.tree.Node;
20  import org.kuali.rice.krms.api.repository.LogicalOperator;
21  import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType;
22  import org.kuali.rice.krms.api.repository.proposition.PropositionType;
23  import org.kuali.rice.krms.dto.PropositionEditor;
24  import org.kuali.rice.krms.dto.PropositionParameterEditor;
25  import org.kuali.rice.krms.dto.RuleEditor;
26  import org.kuali.rice.krms.tree.node.RuleEditorTreeNode;
27  
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  
32  /**
33   * Utility class with common functionality on the proposition tree.
34   *
35   * @author Kuali Student Team
36   */
37  public class PropositionTreeUtil {
38  
39      public static final String DOC_NEW_DATAOBJECT_PATH = "document.newMaintainableObject.dataObject";
40      public static final String EDIT_TREE_NEW_COLLECTION_LINE = DOC_NEW_DATAOBJECT_PATH + ".editTree";
41  
42      public static String getBindingPath(PropositionEditor proposition, String propertyName) {
43          return getBindingPrefix(proposition) + "proposition." + propertyName;
44      }
45  
46      public static String getBindingPrefix(PropositionEditor proposition) {
47          return DOC_NEW_DATAOBJECT_PATH + "." + proposition.getBindingPath() + ".";
48      }
49  
50      public static Node<RuleEditorTreeNode, String> findParentPropositionNode(Node<RuleEditorTreeNode, String> currentNode, String selectedPropKey) {
51          Node<RuleEditorTreeNode, String> bingo = null;
52          if (selectedPropKey != null) {
53              // if it's in children, we have the parent
54              List<Node<RuleEditorTreeNode, String>> children = currentNode.getChildren();
55              for (Node<RuleEditorTreeNode, String> child : children) {
56                  RuleEditorTreeNode dataNode = child.getData();
57                  if (selectedPropKey.equalsIgnoreCase(dataNode.getProposition().getKey())) {
58                      return currentNode;
59                  }
60              }
61  
62              // if not found check grandchildren
63              for (Node<RuleEditorTreeNode, String> kid : children) {
64                  bingo = findParentPropositionNode(kid, selectedPropKey);
65                  if (bingo != null) {
66                      break;
67                  }
68              }
69          }
70          return bingo;
71      }
72  
73      /**
74       * @return the {@link org.kuali.rice.krms.impl.repository.PropositionBo} from the form
75       */
76      public static PropositionEditor getProposition(RuleEditor ruleEditor) {
77  
78          if (ruleEditor != null) {
79              String selectedPropKey = ruleEditor.getSelectedKey();
80              return findProposition(ruleEditor.getEditTree().getRootElement(), selectedPropKey);
81          }
82  
83          return null;
84      }
85  
86      public static PropositionEditor findProposition(Node<RuleEditorTreeNode, String> currentNode, String selectedPropKey) {
87  
88          if ((selectedPropKey == null) || (selectedPropKey.isEmpty())) {
89              return null;
90          }
91  
92          // if it's in children, we have the parent
93          for (Node<RuleEditorTreeNode, String> child : currentNode.getChildren()) {
94              PropositionEditor proposition = child.getData().getProposition();
95              if (selectedPropKey.equalsIgnoreCase(proposition.getKey())) {
96                  return proposition;
97              } else if ("S".equals(proposition.getPropositionTypeCode()) && proposition.isEditMode()) {
98                  return proposition;
99              } 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 compounde 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().size() == 1) {
276             rule.setProposition(root.getCompoundEditors().get(0));
277         }
278 
279         //Remove single parent from proposition tree.
280         removeCompoundProp(rule.getPropositionEditor());
281 
282     }
283 
284     /**
285      * This method walks through the proposition tree and remove all parent compound propositions
286      * that only have one child.
287      *
288      * Note: This does not handle the root as the root need to be set on the rule.
289      *
290      * @param proposition
291      */
292     public static void removeCompoundProp(PropositionEditor proposition){
293         if (proposition.getCompoundEditors() != null) {
294 
295             // Handle the scenario if the inpust proposition only have one child.
296             if(proposition.getCompoundEditors().size()==1){
297                 PropositionEditor child = proposition.getCompoundEditors().get(0);
298                 if(PropositionType.COMPOUND.getCode().equalsIgnoreCase(child.getPropositionTypeCode())) {
299                     proposition.setCompoundEditors(child.getCompoundEditors());
300                 }
301             }
302 
303             // Now handle the children.
304             for(int i=proposition.getCompoundEditors().size()-1;i>=0;i--){
305                 PropositionEditor child = proposition.getCompoundEditors().get(i);
306                 if(PropositionType.COMPOUND.getCode().equalsIgnoreCase(child.getPropositionTypeCode())) {
307                     if (child.getCompoundEditors().isEmpty()) {
308                         proposition.getCompoundEditors().remove(i);
309                     } else {
310                         if (child.getCompoundEditors().size() == 1) {
311                             proposition.getCompoundEditors().set(i, child.getCompoundEditors().get(0));
312                         }
313                         removeCompoundProp(child);
314                     }
315                 }
316             }
317         }
318     }
319 
320     public static void resetNewProp(PropositionEditor proposition) {
321         proposition.setNewProp(false);
322         if (proposition.getCompoundEditors() != null) {
323             for (PropositionEditor child : proposition.getCompoundEditors()) {
324                 resetNewProp(child);
325             }
326         }
327     }
328 
329     /**
330      * Returns the first parameter of type term in the list.
331      *
332      * @param parameters
333      * @return
334      */
335     public static PropositionParameterEditor getTermParameter(List<PropositionParameterEditor> parameters) {
336         return getParameterForType(parameters, PropositionParameterType.TERM);
337     }
338 
339     /**
340      * Returns the first parameter of type constant in the list.
341      *
342      * @param parameters
343      * @return
344      */
345     public static PropositionParameterEditor getConstantParameter(List<PropositionParameterEditor> parameters) {
346         return getParameterForType(parameters, PropositionParameterType.CONSTANT);
347     }
348 
349     /**
350      * Returns the first parameter of type operator in the list.
351      *
352      * @param parameters
353      * @return
354      */
355     public static PropositionParameterEditor getOperatorParameter(List<PropositionParameterEditor> parameters) {
356         return getParameterForType(parameters, PropositionParameterType.OPERATOR);
357     }
358 
359     /**
360      * Returns the first parameter of the given type in the list.
361      *
362      * @param parameters
363      * @param type
364      * @return
365      */
366     public static PropositionParameterEditor getParameterForType(List<PropositionParameterEditor> parameters, PropositionParameterType type) {
367 
368         if (parameters == null) {
369             return null;
370         }
371 
372         for (PropositionParameterEditor parameter : parameters) {
373             if (type.getCode().equals(parameter.getParameterType())) {
374                 return parameter;
375             }
376         }
377         return null;
378     }
379 
380     /**
381      * Returns a label based on the operator code.
382      *
383      * @param opCode
384      * @return
385      */
386     public static String getLabelForOperator(String opCode) {
387         if (LogicalOperator.AND.getCode().equalsIgnoreCase(opCode)) {
388             return "AND";
389         } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(opCode)) {
390             return "OR";
391         }
392         return StringUtils.EMPTY;
393     }
394 
395     public static boolean isSimpleCompounds(PropositionEditor propositionEditor) {
396         for (int index = 0; index < propositionEditor.getCompoundEditors().size(); index++) {
397             if (propositionEditor.getCompoundEditors().get(index).getPropositionTypeCode().equals("C")) {
398                 return false;
399             }
400         }
401         return true;
402     }
403 }