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}