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 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}