package org.activityinfo.ui.client.component.formdesigner.skip; /* * #%L * ActivityInfo Server * %% * Copyright (C) 2009 - 2013 UNICEF * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.activityinfo.model.expr.*; import org.activityinfo.model.expr.functions.BooleanFunctions; import org.activityinfo.model.expr.functions.ExprFunction; import org.activityinfo.model.form.FormClass; import org.activityinfo.model.form.FormField; import org.activityinfo.model.resource.ResourceId; import org.activityinfo.model.type.FieldType; import org.activityinfo.model.type.HasSetFieldValue; import org.activityinfo.model.type.ReferenceType; import org.activityinfo.model.type.ReferenceValue; import org.activityinfo.model.type.enumerated.EnumValue; import org.activityinfo.model.type.enumerated.EnumType; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * @author yuriyz on 7/28/14. */ public class RowDataBuilder { private static final Logger LOGGER = Logger.getLogger(RowDataBuilder.class.getName()); public static final ExprFunction DEFAULT_JOIN_FUNCTION = BooleanFunctions.AND; private List<RowData> rows = Lists.newArrayList(); // keep list, order is important! private FormClass formClass; public RowDataBuilder(FormClass formClass) { this.formClass = formClass; } public List<RowData> build(String skipExpression) { try { ExprLexer lexer = new ExprLexer(skipExpression); ExprParser parser = new ExprParser(lexer); ExprNode node = parser.parse(); parse(node, DEFAULT_JOIN_FUNCTION); } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); // 1. FormField was removed ? // 2. other reason ? // we don't want to block user, show dialog without rows. return Lists.newArrayList(); } return rows; } private void parse(ExprNode node, ExprFunction joinFunction) { if (node instanceof GroupExpr) { node = ((GroupExpr) node).getExpr(); } // handle Function call node with at least one argument if (node instanceof FunctionCallNode && ((FunctionCallNode) node).getArguments().size() > 0) { final FunctionCallNode functionCallNode = (FunctionCallNode) node; if (ExprParser.FUNCTIONS.contains(functionCallNode.getFunction().getId())) { FormField field = formClass.getField(ResourceId.valueOf(placeholder(unwrap(functionCallNode.getArguments().get(0))))); parseRowWithFunction(joinFunction, functionCallNode, field); return; } else if (functionCallNode.getArguments().size() == 2) { // for non-function node it's expected to have exactly 2 arguments! ExprNode arg1 = unwrap(functionCallNode.getArguments().get(0)); ExprNode arg2 = unwrap(functionCallNode.getArguments().get(1)); if (arg1 instanceof SymbolExpr) { if (isFieldFunction(functionCallNode.getFunction())) { FormField field = formClass.getField(ResourceId.valueOf(placeholder(unwrap(functionCallNode.getArguments().get(0))))); final RowData row = getOrCreateRow(field); row.setFunction(functionCallNode.getFunction()); row.setJoinFunction(joinFunction); if (setValueInRow(row, arg2)) { return; } else if (arg2 instanceof FunctionCallNode) { final FunctionCallNode arg2Node = (FunctionCallNode) arg2; if (isFieldFunction(arg2Node.getFunction())) { parse(arg2Node, arg2Node.getFunction()); return; } else { // not field function -> means &&, || (but not ==, !=) final ExprNode nestArg1 = arg2Node.getArguments().get(0); final ExprNode nestArg2 = arg2Node.getArguments().get(1); setValueInRow(row, nestArg1); if (nestArg2 instanceof FunctionCallNode || nestArg2 instanceof GroupExpr) { parse(nestArg2, arg2Node.getFunction()); return; } else { throw new UnsupportedOperationException(); } } } } } else if (arg1 instanceof FunctionCallNode) { // parse flat structure for (Object exprNode : functionCallNode.getArguments()) { FunctionCallNode unwrappedNode = (FunctionCallNode) unwrap((ExprNode) exprNode); if (isFieldFunction(unwrappedNode.getFunction())) { ExprNode unwrappedArg1 = unwrappedNode.getArguments().get(0); ExprNode unwrappedArg2 = unwrappedNode.getArguments().get(1); final FormField field = formClass.getField(ResourceId.valueOf(placeholder(unwrappedArg1))); final RowData row = getOrCreateRow(field); row.setFunction(unwrappedNode.getFunction()); row.setJoinFunction(functionCallNode.getFunction()); setValueInRow(row, unwrappedArg2); } else { parse(unwrappedNode, unwrappedNode.getFunction()); } } return; } } } throw new UnsupportedOperationException(); } private void parseRowWithFunction(ExprFunction joinFunction, FunctionCallNode functionCallNode, FormField field) { final RowData row = getOrCreateRow(field); row.setFunction(functionCallNode.getFunction()); row.setJoinFunction(joinFunction); // set value FieldType type = field.getType(); // start from second element, first one is field id Set<ResourceId> resourceIdSet = Sets.newHashSet(); for (int i = 1; i < functionCallNode.getArguments().size(); i++) { ExprNode argNode = functionCallNode.getArguments().get(i); if (argNode instanceof SymbolExpr) { String symbol = ((SymbolExpr) argNode).getName(); resourceIdSet.add(ResourceId.valueOf(symbol)); } else { throw new UnsupportedOperationException("Unknown argument node for function: " + functionCallNode.getFunction().getId()); } } if (type instanceof ReferenceType) { row.setValue(new ReferenceValue(resourceIdSet)); } else if (type instanceof EnumType) { row.setValue(new EnumValue(resourceIdSet)); } else { throw new UnsupportedOperationException("Unknown value type for function: " + functionCallNode.getFunction().getId()); } } private RowData getOrCreateRow(FormField formField) { // search, maybe row is already present for (RowData row : rows) { if (row.getFormField().equals(formField)) { return row; } } // create new row RowData row = new RowData(); row.setFormField(formField); rows.add(row); return row; } private static ExprNode unwrap(ExprNode node) { if (node instanceof GroupExpr) { return ((GroupExpr) node).getExpr(); } return node; } /** * Returns whether value was set in row or not. * * @param row row * @param node node * @return Returns whether value was set in row or not */ private static boolean setValueInRow(RowData row, ExprNode node) { if (node instanceof ConstantExpr) { row.setValue(((ConstantExpr) node).getValue()); return true; } else if (node instanceof SymbolExpr) { ResourceId newItem = ResourceId.valueOf(placeholder(node)); if (row.getValue() instanceof HasSetFieldValue) { // update existing value HasSetFieldValue oldValue = (HasSetFieldValue) row.getValue(); Set<ResourceId> newValue = Sets.newHashSet(oldValue.getResourceIds()); newValue.add(newItem); if (row.getFormField().getType() instanceof EnumType) { row.setValue(new EnumValue(newValue)); } else if (row.getFormField().getType() instanceof ReferenceType) { row.setValue(new ReferenceValue(newValue)); } } else { // create value if (row.getFormField().getType() instanceof EnumType) { row.setValue(new EnumValue(newItem)); } else if (row.getFormField().getType() instanceof ReferenceType) { row.setValue(new ReferenceValue(newItem)); } else { throw new UnsupportedOperationException(row.getFormField().getType() + " is not supported."); } } return true; } return false; } private static boolean isFieldFunction(ExprFunction exprFunction) { return exprFunction == BooleanFunctions.EQUAL || exprFunction == BooleanFunctions.NOT_EQUAL; } private static String placeholder(ExprNode node) { SymbolExpr symbolExpr = (SymbolExpr) node; return symbolExpr.getName(); } }