/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* 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.
*/
package org.drools.workbench.models.guided.template.backend;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.drools.core.util.IoUtils;
import org.drools.template.DataProvider;
import org.drools.template.DataProviderCompiler;
import org.drools.template.objects.ArrayDataProvider;
import org.drools.template.parser.DefaultTemplateContainer;
import org.drools.template.parser.TemplateDataListener;
import org.drools.workbench.models.commons.backend.rule.RuleModelDRLPersistenceImpl;
import org.drools.workbench.models.commons.backend.rule.RuleModelIActionPersistenceExtension;
import org.drools.workbench.models.commons.backend.rule.RuleModelPersistence;
import org.drools.workbench.models.commons.backend.rule.context.LHSGeneratorContext;
import org.drools.workbench.models.commons.backend.rule.context.LHSGeneratorContextFactory;
import org.drools.workbench.models.commons.backend.rule.context.RHSGeneratorContext;
import org.drools.workbench.models.commons.backend.rule.context.RHSGeneratorContextFactory;
import org.drools.workbench.models.datamodel.rule.ActionFieldValue;
import org.drools.workbench.models.datamodel.rule.BaseSingleFieldConstraint;
import org.drools.workbench.models.datamodel.rule.CompositeFieldConstraint;
import org.drools.workbench.models.datamodel.rule.ConnectiveConstraint;
import org.drools.workbench.models.datamodel.rule.ExpressionFormLine;
import org.drools.workbench.models.datamodel.rule.FieldConstraint;
import org.drools.workbench.models.datamodel.rule.FieldNatureType;
import org.drools.workbench.models.datamodel.rule.FreeFormLine;
import org.drools.workbench.models.datamodel.rule.FromCollectCompositeFactPattern;
import org.drools.workbench.models.datamodel.rule.IFactPattern;
import org.drools.workbench.models.datamodel.rule.InterpolationVariable;
import org.drools.workbench.models.datamodel.rule.RuleModel;
import org.drools.workbench.models.datamodel.rule.SingleFieldConstraint;
import org.drools.workbench.models.datamodel.rule.builder.DRLConstraintValueBuilder;
import org.drools.workbench.models.guided.template.shared.TemplateModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class persists a {@link TemplateModel} to DRL template
*/
public class RuleTemplateModelDRLPersistenceImpl
extends RuleModelDRLPersistenceImpl {
private static final Pattern patternTemplateKey = Pattern.compile("@\\{(.+?)\\}");
private static final Logger log = LoggerFactory.getLogger(RuleTemplateModelDRLPersistenceImpl.class);
private static final RuleModelPersistence INSTANCE = new RuleTemplateModelDRLPersistenceImpl();
private RuleTemplateModelDRLPersistenceImpl() {
super();
}
public static RuleModelPersistence getInstance() {
return INSTANCE;
}
@Override
protected LHSPatternVisitor getLHSPatternVisitor(final boolean isDSLEnhanced,
final StringBuilder buf,
final String nestedIndentation,
final boolean isNegated,
final LHSGeneratorContextFactory generatorContextFactory) {
return new LHSPatternVisitor(isDSLEnhanced,
bindingsPatterns,
bindingsFields,
constraintValueBuilder,
generatorContextFactory,
buf,
nestedIndentation,
isNegated);
}
@Override
protected RHSActionVisitor getRHSActionVisitor(final boolean isDSLEnhanced,
final StringBuilder buf,
final String indentation,
final RHSGeneratorContextFactory generatorContextFactory,
final Collection<RuleModelIActionPersistenceExtension> extensions) {
return new RHSActionVisitor(isDSLEnhanced,
bindingsPatterns,
bindingsFields,
constraintValueBuilder,
generatorContextFactory,
extensions,
buf,
indentation);
}
public static class LHSPatternVisitor extends RuleModelDRLPersistenceImpl.LHSPatternVisitor {
public LHSPatternVisitor(final boolean isDSLEnhanced,
final Map<String, IFactPattern> bindingsPatterns,
final Map<String, FieldConstraint> bindingsFields,
final DRLConstraintValueBuilder constraintValueBuilder,
final LHSGeneratorContextFactory generatorContextFactory,
final StringBuilder b,
final String indentation,
final boolean isPatternNegated) {
super(isDSLEnhanced,
bindingsPatterns,
bindingsFields,
constraintValueBuilder,
generatorContextFactory,
b,
indentation,
isPatternNegated);
}
@Override
protected void preGeneratePattern(final LHSGeneratorContext gctx) {
buf.append("@if{(");
if (gctx.getVarsInScope().size() == 0) {
buf.append("true)}");
} else {
for (String var : gctx.getVarsInScope()) {
buf.append(var + " != empty || ");
}
buf.delete(buf.length() - 4,
buf.length());
buf.append(") || hasLHSNonTemplateOutput").append(gctx.getDepth() + "_" + gctx.getOffset()).append("}");
}
}
@Override
protected void postGeneratePattern(final LHSGeneratorContext gctx) {
buf.append("@end{}");
}
@Override
protected void preGenerateNestedConnector(final LHSGeneratorContext gctx) {
if (gctx.getVarsInScope().size() > 0) {
buf.append("@if{(");
for (String var : gctx.getVarsInScope()) {
buf.append(var + " != empty || ");
}
buf.delete(buf.length() - 4,
buf.length());
LHSGeneratorContext parentContext = gctx.getParent();
if (parentContext != null) {
Set<String> parentVarsInScope = new HashSet<String>(parentContext.getVarsInScope());
parentVarsInScope.removeAll(gctx.getVarsInScope());
if (parentVarsInScope.size() > 0) {
buf.append(") && !(");
for (String var : parentVarsInScope) {
buf.append(var + " == empty && ");
}
buf.delete(buf.length() - 4,
buf.length());
}
}
int ctxOffset = gctx.getOffset();
if (ctxOffset > 0) {
buf.append(") || (");
do {
ctxOffset--;
buf.append("hasLHSNonTemplateOutput" + gctx.getDepth() + "_" + ctxOffset + " || ");
}
while (ctxOffset > 0);
buf.delete(buf.length() - 4,
buf.length());
buf.append(")}");
}
} else {
int ctxOffset = gctx.getOffset();
if (ctxOffset > 0) {
buf.append("@if{");
do {
ctxOffset--;
buf.append("hasLHSOutput" + gctx.getDepth() + "_" + ctxOffset + " || ");
}
while (ctxOffset > 0);
buf.delete(buf.length() - 4,
buf.length());
buf.append("}");
}
}
}
@Override
protected void postGenerateNestedConnector(final LHSGeneratorContext gctx) {
if (gctx.getVarsInScope().size() > 0) {
buf.append("@end{}");
} else {
int ctxOffset = gctx.getOffset();
if (ctxOffset > 0) {
buf.append("@end{}");
}
}
}
@Override
protected void preGenerateNestedConstraint(final LHSGeneratorContext gctx) {
if (gctx.getVarsInScope().size() > 0) {
buf.append("@if{!(");
for (String var : gctx.getVarsInScope()) {
buf.append(var + " == empty && ");
}
buf.delete(buf.length() - 4,
buf.length());
buf.append(") || hasLHSNonTemplateOutput").append(gctx.getDepth() + "_" + gctx.getOffset()).append("}");
}
}
@Override
protected void postGenerateNestedConstraint(final LHSGeneratorContext gctx) {
if (gctx.getVarsInScope().size() > 0) {
buf.append("@end{}");
}
}
@Override
protected void generateConstraint(final FieldConstraint constr,
final LHSGeneratorContext gctx) {
boolean generateTemplateCheck = isTemplateKey(constr);
if (generateTemplateCheck) {
if (constr instanceof SingleFieldConstraint && ((SingleFieldConstraint) constr).getConnectives() != null) {
// if there are connectives, and the first is a template key, then all templates keys must be checked up front
// individual connectives, that have template keys, will still need to be checked too.
SingleFieldConstraint sconstr = (SingleFieldConstraint) constr;
buf.append("@if{" + ((SingleFieldConstraint) constr).getValue() + " != empty");
for (int j = 0; j < sconstr.getConnectives().length; j++) {
final ConnectiveConstraint conn = sconstr.getConnectives()[j];
if (conn.getConstraintValueType() == BaseSingleFieldConstraint.TYPE_TEMPLATE) {
buf.append(" || " + conn.getValue() + " != empty");
}
}
buf.append("}");
} else {
buf.append("@if{" + ((SingleFieldConstraint) constr).getValue() + " != empty}");
}
}
buf.append("@code{hasLHSOutput" + gctx.getDepth() + "_" + gctx.getOffset() + " = true}");
super.generateConstraint(constr,
gctx);
if (generateTemplateCheck) {
buf.append("@end{}");
}
}
private boolean isTemplateKey(final FieldConstraint nestedConstr) {
return nestedConstr instanceof BaseSingleFieldConstraint && ((BaseSingleFieldConstraint) nestedConstr).getConstraintValueType() == BaseSingleFieldConstraint.TYPE_TEMPLATE;
}
public void generateSeparator(final FieldConstraint constr,
final LHSGeneratorContext gctx) {
final List<LHSGeneratorContext> peers = generatorContextFactory.getPeers(gctx);
if (peers.size() == 0) {
return;
}
boolean generateTemplateCheck = isTemplateKey(constr);
if (generateTemplateCheck) {
buf.append("@if{(");
buf.append("hasLHSOutput" + gctx.getDepth() + "_" + gctx.getOffset()).append(" && (");
for (int i = 0; i < peers.size(); i++) {
final LHSGeneratorContext peer = peers.get(i);
buf.append("hasLHSOutput" + peer.getDepth() + "_" + peer.getOffset());
buf.append((i < peers.size() - 1 ? " || " : ")"));
}
buf.append(")}");
}
preGenerateNestedConnector(gctx);
if (gctx.getParent().getFieldConstraint() instanceof CompositeFieldConstraint) {
CompositeFieldConstraint cconstr = (CompositeFieldConstraint) gctx.getParent().getFieldConstraint();
buf.append(cconstr.getCompositeJunctionType() + " ");
} else {
if (buf.length() > 2 && !(buf.charAt(buf.length() - 2) == ',')) {
buf.append(", ");
}
}
postGenerateNestedConnector(gctx);
if (generateTemplateCheck) {
buf.append("@end{}");
}
}
@Override
protected void addConnectiveFieldRestriction(final StringBuilder buf,
final int type,
final String fieldType,
final String operator,
final Map<String, String> parameters,
final String value,
final ExpressionFormLine expression,
final LHSGeneratorContext gctx,
boolean spaceBeforeOperator) {
boolean generateTemplateCheck = type == BaseSingleFieldConstraint.TYPE_TEMPLATE;
if (generateTemplateCheck) {
buf.append("@if{" + value + " != empty}");
}
String _operator = operator;
if (generateTemplateCheck && _operator.startsWith("||") || _operator.startsWith("&&")) {
spaceBeforeOperator = false;
buf.append("@if{hasLHSOutput" + gctx.getDepth() + "_" + gctx.getOffset() + "} ");// add space here, due to split operator
buf.append(_operator.substring(0,
2));
buf.append("@end{}");
_operator = _operator.substring(2);
}
super.addConnectiveFieldRestriction(buf,
type,
fieldType,
_operator,
parameters,
value,
expression,
gctx,
spaceBeforeOperator);
if (generateTemplateCheck) {
buf.append("@code{hasLHSOutput" + gctx.getDepth() + "_" + gctx.getOffset() + " = true}");
buf.append("@end{}");
}
}
@Override
public void visitFreeFormLine(final FreeFormLine ffl) {
if (ffl.getText() == null) {
return;
}
final Matcher matcherTemplateKey = patternTemplateKey.matcher(ffl.getText());
boolean found = matcherTemplateKey.find();
if (found) {
buf.append("@if{");
boolean addAnd = false;
while (found) {
String varName = matcherTemplateKey.group(1);
if (addAnd) {
buf.append(" && ");
}
buf.append(varName + " != empty");
addAnd = true;
found = matcherTemplateKey.find();
}
buf.append("}");
super.visitFreeFormLine(ffl);
buf.append("@end{}");
} else {
// no variables found
super.visitFreeFormLine(ffl);
}
}
@Override
public void visitFromCollectCompositeFactPattern(final FromCollectCompositeFactPattern pattern,
final boolean isSubPattern) {
if (pattern.getRightPattern() instanceof FreeFormLine) {
// this allows MVEL to skip the collect, if any vars are empty
// note this actually duplicates another inner check for the FFL itself
// @TODO the FFL should get a reference to the parent, so it can avoid this duplication.
final FreeFormLine ffl = (FreeFormLine) pattern.getRightPattern();
if (ffl.getText() == null) {
return;
}
final Matcher matcherTemplateKey = patternTemplateKey.matcher(ffl.getText());
boolean found = matcherTemplateKey.find();
if (found) {
buf.append("@if{");
boolean addAnd = false;
while (found) {
String varName = matcherTemplateKey.group(1);
if (addAnd) {
buf.append(" && ");
}
buf.append(varName + " != empty");
addAnd = true;
found = matcherTemplateKey.find();
}
buf.append("}");
super.visitFromCollectCompositeFactPattern(pattern,
isSubPattern);
buf.append("@end{}");
found = matcherTemplateKey.find();
} else {
// no variables found
super.visitFromCollectCompositeFactPattern(pattern,
isSubPattern);
}
} else {
super.visitFromCollectCompositeFactPattern(pattern,
isSubPattern);
}
}
}
public static class RHSActionVisitor extends RuleModelDRLPersistenceImpl.RHSActionVisitor {
public RHSActionVisitor(final boolean isDSLEnhanced,
final Map<String, IFactPattern> bindingsPatterns,
final Map<String, FieldConstraint> bindingsFields,
final DRLConstraintValueBuilder constraintValueBuilder,
final RHSGeneratorContextFactory generatorContextFactory,
final Collection<RuleModelIActionPersistenceExtension> extensions,
final StringBuilder b,
final String indentation) {
super(isDSLEnhanced,
bindingsPatterns,
bindingsFields,
constraintValueBuilder,
generatorContextFactory,
extensions,
b,
indentation);
}
@Override
protected void preGenerateAction(final RHSGeneratorContext gctx) {
buf.append("@if{(");
if (gctx.getVarsInScope().size() == 0) {
buf.append("true)}");
} else {
for (String var : gctx.getVarsInScope()) {
buf.append(var + " != empty || ");
}
buf.delete(buf.length() - 4,
buf.length());
buf.append(") || hasRHSNonTemplateOutput").append(gctx.getDepth() + "_" + gctx.getOffset()).append("}");
}
}
@Override
protected void postGenerateAction(final RHSGeneratorContext gctx) {
buf.append("@end{}");
}
@Override
protected void preGenerateSetMethodCallParameterValue(final RHSGeneratorContext gctx,
final ActionFieldValue fieldValue) {
if (fieldValue.getNature() == FieldNatureType.TYPE_TEMPLATE) {
buf.append("@if{" + fieldValue.getValue() + " != empty}");
buf.append("@code{hasRHSOutput" + gctx.getDepth() + "_" + gctx.getOffset() + " = true}");
super.preGenerateSetMethodCallParameterValue(gctx,
fieldValue);
buf.append("@end{}");
} else {
buf.append("@code{hasRHSOutput" + gctx.getDepth() + "_" + gctx.getOffset() + " = true}");
super.preGenerateSetMethodCallParameterValue(gctx,
fieldValue);
}
}
@Override
protected void generateSetMethodCall(final String variableName,
final ActionFieldValue fieldValue) {
if (fieldValue.getNature() == FieldNatureType.TYPE_TEMPLATE) {
buf.append("@if{" + fieldValue.getValue() + " != empty}");
super.generateSetMethodCall(variableName,
fieldValue);
buf.append("@end{}");
} else {
super.generateSetMethodCall(variableName,
fieldValue);
}
}
@Override
protected void generateModifyMethodCall(final RHSGeneratorContext gctx,
final ActionFieldValue fieldValue) {
if (fieldValue.getNature() == FieldNatureType.TYPE_TEMPLATE) {
buf.append("@if{" + fieldValue.getValue() + " != empty}");
super.generateModifyMethodCall(gctx,
fieldValue);
buf.append("@end{}");
} else {
super.generateModifyMethodCall(gctx,
fieldValue);
}
}
@Override
protected void generateModifyMethodSeparator(final RHSGeneratorContext gctx,
final ActionFieldValue fieldValue) {
final List<RHSGeneratorContext> peers = generatorContextFactory.getPeers(gctx);
if (peers.size() == 0) {
return;
}
buf.append("@if{(");
buf.append("hasRHSOutput" + gctx.getDepth() + "_" + gctx.getOffset()).append(" && (");
for (int i = 0; i < peers.size(); i++) {
final RHSGeneratorContext peer = peers.get(i);
buf.append("hasRHSOutput" + peer.getDepth() + "_" + peer.getOffset());
buf.append((i < peers.size() - 1 ? " || " : ")"));
}
buf.append(")}");
buf.append(", \n");
buf.append("@end{}");
}
@Override
public void visitFreeFormLine(final FreeFormLine ffl) {
if (ffl.getText() == null) {
return;
}
final Matcher matcherTemplateKey = patternTemplateKey.matcher(ffl.getText());
boolean found = matcherTemplateKey.find();
if (found) {
buf.append("@if{");
boolean addAnd = false;
while (found) {
String varName = matcherTemplateKey.group(1);
if (addAnd) {
buf.append(" && ");
}
buf.append(varName + " != empty");
addAnd = true;
found = matcherTemplateKey.find();
}
buf.append("}");
super.visitFreeFormLine(ffl);
buf.append("@end{}");
} else {
// no variables found
super.visitFreeFormLine(ffl);
}
}
}
@Override
public String marshal(final RuleModel model) {
//Build rule
final String ruleTemplate = marshalRule(model,
Collections.emptyList());
log.debug("ruleTemplate:\n{}",
ruleTemplate);
log.debug("generated template:\n{}",
ruleTemplate);
final DataProvider dataProvider = chooseDataProvider(model);
final DataProviderCompiler tplCompiler = new DataProviderCompiler();
final InputStream templateStream = new ByteArrayInputStream(ruleTemplate.getBytes(IoUtils.UTF8_CHARSET));
final DefaultTemplateContainer tc = new DefaultTemplateContainer(templateStream,
false);
final TemplateDataListener listener = new TemplateDataListener(tc,
false);
final String generatedDrl = tplCompiler.compile(dataProvider,
listener);
log.debug("generated drl:\n{}",
generatedDrl);
return generatedDrl;
}
@Override
protected String marshalRule(final RuleModel model,
final Collection<RuleModelIActionPersistenceExtension> extensions) {
boolean isDSLEnhanced = model.hasDSLSentences();
bindingsPatterns = new HashMap<String, IFactPattern>();
bindingsFields = new HashMap<String, FieldConstraint>();
fixActionInsertFactBindings(model.rhs);
StringBuilder buf = new StringBuilder();
StringBuilder header = new StringBuilder();
LHSGeneratorContextFactory lhsGeneratorContextFactory = new LHSGeneratorContextFactory();
RHSGeneratorContextFactory rhsGeneratorContextFactory = new RHSGeneratorContextFactory();
//Build rule
this.marshalRuleHeader(model,
header);
super.marshalMetadata(buf,
model);
super.marshalAttributes(buf,
model);
buf.append("\twhen\n");
super.marshalLHS(buf,
model,
isDSLEnhanced,
lhsGeneratorContextFactory);
buf.append("\tthen\n");
super.marshalRHS(buf,
model,
isDSLEnhanced,
rhsGeneratorContextFactory,
extensions);
this.marshalFooter(buf);
for (LHSGeneratorContext gc : lhsGeneratorContextFactory.getGeneratorContexts()) {
header.append("@code{hasLHSOutput" + gc.getDepth() + "_" + gc.getOffset() + " = false}");
header.append("@code{hasLHSNonTemplateOutput" + gc.getDepth() + "_" + gc.getOffset() + " = " + gc.hasNonTemplateOutput() + "}");
}
for (RHSGeneratorContext gc : rhsGeneratorContextFactory.getGeneratorContexts()) {
header.append("@code{hasRHSOutput" + gc.getDepth() + "_" + gc.getOffset() + " = false}");
header.append("@code{hasRHSNonTemplateOutput" + gc.getDepth() + "_" + gc.getOffset() + " = " + gc.hasNonTemplateOutput() + "}");
}
header.append("@code{\n" +
" def removeDelimitingQuotes(value) {\n" +
" if(value.startsWith('\"') && value.endsWith('\"')) {\n" +
" return value.substring(1, value.length() - 1);\n" +
" }\n" +
" value;\n" +
" }\n" +
"}");
header.append("@code{\n" +
"def capitals(value) {\n" +
" value.toUpperCase();\n" +
"}}");
header.append("@code{\n" +
" def makeValueList(value) {\n" +
" if(value.startsWith('\"') && value.endsWith('\"')) {\n" +
" value = value.substring(1, value.length() - 1);\n" +
" }\n" +
" workingValue = value.trim();\n" +
" if ( workingValue.startsWith('(') ) {\n" +
" workingValue = workingValue.substring( 1 );\n" +
" }\n" +
" if ( workingValue.endsWith(')') ) {\n" +
" workingValue = workingValue.substring( 0," +
" workingValue.length() - 1 );\n" +
" }\n" +
" values = workingValue.split( ',' );\n" +
" output = ' (';\n" +
" for (v : values ) {\n" +
" v = v.trim();\n" +
" if ( v.startsWith( '\"' ) ) {\n" +
" v = v.substring( 1 );\n" +
" }\n" +
" if ( v.endsWith( '\"' ) ) {\n" +
" v = v.substring( 0,v.length() - 1 );\n" +
" }\n" +
" output+='\"'+v+'\", ';\n" +
" }" +
" output=output.substring(0,output.length()-2)+')';\n" +
" output;\n" +
" }\n" +
"}");
return header.append(buf).toString();
}
private DataProvider chooseDataProvider(final RuleModel model) {
DataProvider dataProvider;
TemplateModel tplModel = (TemplateModel) model;
if (tplModel.getRowsCount() > 0) {
dataProvider = new ArrayDataProvider(tplModel.getTableAsArray());
} else {
dataProvider = generateEmptyIterator();
}
return dataProvider;
}
private DataProvider generateEmptyIterator() {
return new DataProvider() {
public boolean hasNext() {
return false;
}
public String[] next() {
return new String[0];
}
};
}
@Override
protected void marshalRuleHeader(final RuleModel model,
final StringBuilder buf) {
//Append Template header
TemplateModel templateModel = (TemplateModel) model;
buf.append("template header\n");
InterpolationVariable[] interpolationVariables = templateModel.getInterpolationVariablesList();
if (interpolationVariables.length == 0) {
buf.append("test_var").append('\n');
} else {
for (InterpolationVariable var : interpolationVariables) {
buf.append(var.getVarName()).append('\n');
}
}
buf.append("\n");
//Append Package header
super.marshalPackageHeader(model,
buf);
//Append Template definition
buf.append("\ntemplate \"").append(super.marshalRuleName(templateModel)).append("\"\n\n");
super.marshalRuleHeader(model,
buf);
}
@Override
protected String marshalRuleName(final RuleModel model) {
return super.marshalRuleName(model) + "_@{row.rowNumber}";
}
@Override
protected void marshalFooter(final StringBuilder buf) {
super.marshalFooter(buf);
buf.append("\nend template");
}
}