/* * #%L * BroadleafCommerce Open Admin Platform * %% * Copyright (C) 2009 - 2013 Broadleaf Commerce * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package org.broadleafcommerce.openadmin.web.rulebuilder.grouping; import org.broadleafcommerce.openadmin.web.rulebuilder.BLCOperator; import org.broadleafcommerce.openadmin.web.rulebuilder.MVELTranslationException; import java.util.Stack; /** * @author jfischer * @author Elbert Bautista (elbertbautista) */ public class GroupingTranslator { public static final String GROUPSTARTCHAR = "("; public static final String GROUPENDCHAR = ")"; public static final String STATEMENTENDCHAR = ";"; public static final String SPACECHAR = " "; public Group createGroups(String mvel) throws MVELTranslationException { mvel = stripWhiteSpace(mvel); String[] tokens = mvel.trim().split(STATEMENTENDCHAR); if (tokens.length > 1) { throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_RULE, "mvel expressions must resolve to a boolean result. " + "More than one terminated statement has been detected, which does not cumulatively result " + "in a single boolean. Multiple phrases should be strung together into a single expression using " + "standard operators."); } Group topGroup = new Group(); topGroup.setIsTopGroup(true); parseGroups(topGroup, tokens[0]); return topGroup; } protected int findGroupStart(String segment, int startPos) { int startIndex = -1; boolean eof = false; while (!eof) { startIndex = segment.indexOf(GROUPSTARTCHAR, startPos); if (startIndex <= 0) { eof = true; continue; } char preChar = segment.charAt(startIndex-1); if (preChar == '!' || preChar == '&' || preChar == '|') { eof = true; continue; } startPos = startIndex + 1; } return startIndex; } protected int findGroupEnd(String segment, int subgroupStartIndex) throws MVELTranslationException { Stack<Integer> leftParenPos = new Stack<Integer>(); char[] characters = segment.toCharArray(); for (int j=subgroupStartIndex;j<characters.length;j++) { if (characters[j]=='(') { leftParenPos.push(j); continue; } if (characters[j]==')') { leftParenPos.pop(); if(leftParenPos.isEmpty()) { return j + 1; } } } throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_RULE, "Unable to find an end parenthesis for the group started at (" + segment.substring(subgroupStartIndex) + ")"); } protected String stripWhiteSpace(String mvel) { return mvel.replaceAll("[\\t\\n\\r]", ""); } protected void parseGroups(Group myGroup, String segment) throws MVELTranslationException { boolean eol = false; int startPos = 0; while (!eol) { int subgroupStartIndex = -1; boolean isNegation = false; subgroupStartIndex = findGroupStart(segment, startPos); if (subgroupStartIndex == startPos || subgroupStartIndex == startPos + 1) { Group subGroup = new Group(); myGroup.getSubGroups().add(subGroup); int subgroupEndIndex = findGroupEnd(segment, subgroupStartIndex); if (subgroupStartIndex > 0 && segment.charAt(subgroupStartIndex - 1) == '!') { if (myGroup.getIsTopGroup()) { //This is a NOT specified at the topmost level myGroup.setOperatorType(BLCOperator.NOT); } else { //This is a NOT specified on a sub group subGroup.setOperatorType(BLCOperator.NOT); } } parseGroups(subGroup, segment.substring(subgroupStartIndex+1, subgroupEndIndex-1).trim()); startPos = subgroupEndIndex; if (startPos == segment.length()) { eol = true; } else { boolean isAnd = false; boolean isOr = false; if (segment.charAt(startPos) == '&') { isAnd = true; } else if (segment.charAt(startPos) == '|') { isOr = true; } if (myGroup.getOperatorType() == null) { setGroupOperator(segment, myGroup, isAnd, isOr, false); } if (isAnd || isOr) { startPos += 2; } } continue; } else { if (subgroupStartIndex < 0) { compilePhrases(segment.substring(startPos, segment.length()).trim(), myGroup, isNegation); eol = true; continue; } String temp = segment.substring(startPos, subgroupStartIndex); compilePhrases(temp.trim(), myGroup, isNegation); startPos = subgroupStartIndex; continue; } } } protected void compilePhrases(String segment, Group myGroup, boolean isNegation) throws MVELTranslationException { if (segment.trim().length() == 0) { return; } String[] andTokens = segment.split("&&"); String[] orTokens = segment.split("\\|\\|"); if (andTokens.length > 1 && orTokens.length > 1) { throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_RULE, "Segments that mix logical operators are not compatible with " + "the rules builder: (" + segment + ")"); } boolean isAnd = false; boolean isOr = false; boolean isNot = false; if (andTokens.length > 1 || segment.indexOf("&&") >= 0) { isAnd = true; } else if (orTokens.length > 1 || segment.indexOf("||") >= 0) { isOr = true; } if (isAnd && isNegation) { isNot = true; } if (!isAnd && !isOr && !isNot) { isAnd = true; } setGroupOperator(segment, myGroup, isAnd, isOr, isNot); String[] tokens; if (isAnd || isNot) { tokens = andTokens; } else { tokens = orTokens; } for (String token : tokens) { if (token.length() > 0) { myGroup.getPhrases().add(token); } } } protected void setGroupOperator(String segment, Group myGroup, boolean isAnd, boolean isOr, boolean isNot) throws MVELTranslationException { if (myGroup.getOperatorType() == null) { if (isAnd) { myGroup.setOperatorType(BLCOperator.AND); } else if (isOr) { myGroup.setOperatorType(BLCOperator.OR); } else if (isNot) { myGroup.setOperatorType(BLCOperator.NOT); } } else { if ( (isOr && !myGroup.getOperatorType().toString().equals(BLCOperator.OR.toString())) ) { throw new MVELTranslationException(MVELTranslationException.INCOMPATIBLE_RULE, "Segment logical operator is not compatible with the group " + "logical operator: (" + segment + ")"); } } } }