Skip to content

Commit

Permalink
[DROOLS-3348] Support Compiled Feel Expression in the kie-maven-plugin (
Browse files Browse the repository at this point in the history
apache#2173)

* reproducer

* USe maven plugin to generate classes

* Added partner dmn file

* Generate Feel Expression Source

* Generated Feel Expression source

* Read class from source

* Use correct class name

* Refactor

* Also generate input clauses

* First draft of input clause

* INput clause

* Read input clauses

* Refactor

* Refactor

* Refactor

* Moved methods

* Generate rows

* Generate input rowsi

* Input rows

* Fix typo

* Removed class

* Reenabled test

* Fix compilation

* Fix generation

* Update version

* InitRows using JavaParser

* each two there should it be an array

* Fix test

* Input clause with JavaParser

* Removed output clauses

* Fix package

* Moved to separated class

* Removed StringBuilder

* Use JavaParserSourceGenerator

* Removed lambda from FeelEpxressionSourceGenerator

* Some more methods

* Removed yet another part of javaparser

* Source generator (almost) never use Javaparser

* Added license

* Fix regression

* Extracted field

* Generate Feel Expression class only when using it with the Maven Plugin

* Removed useless logger

* Read output clauses

* Avoid checking for exception

* Fix class reading

* Removed duplication

* Iterate over input clauses

* Removed context and interfaces

* Iterate over rows

* Reverted whitespace changes

* Removed method

* Listener to check the number of class generated

* Fix generation of FeelExpression class

* New test for DMN

* Removed test project

* Removed useless code

* Fix after review

* Space after if
  • Loading branch information
lucamolteni authored and mariofusco committed Dec 5, 2018
1 parent d913da9 commit d62a3b7
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ private boolean isDuplicateEvent(DMNModelImpl model, Msg.Message error, DMNEleme
msg.getSourceId().equals( element.getId() ))) );
}

public ClassOrInterfaceDeclaration compileUnaryTests(String unaryTests, DMNCompilerContext ctx, Type inputColumnType) {
public ClassOrInterfaceDeclaration generateUnaryTestsSource(String unaryTests, DMNCompilerContext ctx, Type inputColumnType) {
CompilerContext compilerContext =
ctx.toCompilerContext()
.addInputVariableType("?", inputColumnType);
Expand Down Expand Up @@ -273,4 +273,12 @@ public CompiledExpression compile( DMNModelImpl model, DMNElement element, Msg.M
processEvents( model, element, msg, expr, dtableName, index );
return compiled;
}

public ClassOrInterfaceDeclaration generateFeelExpressionSource(String input, CompilerContext compilerContext1) {

CompilationUnit compilationUnit = ((FEELImpl) feel).generateExpressionSource(input, compilerContext1);
return compilationUnit.getType(0)
.asClassOrInterfaceDeclaration()
.setStatic(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@

package org.kie.dmn.core.compiler.execmodelbased;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.stream.IntStream;

import org.drools.javaparser.ast.body.ClassOrInterfaceDeclaration;
import org.drools.model.functions.Block3;
import org.drools.model.functions.Function2;
import org.kie.dmn.api.core.DMNMessage;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.core.compiler.DMNCompilerContext;
Expand Down Expand Up @@ -62,9 +67,9 @@ public class DTableModel {
private final String tableName;
private final HitPolicy hitPolicy;

private final List<DColumnModel> columns;
private final List<DRowModel> rows;
private final List<DOutputModel> outputs;
protected final List<DColumnModel> columns;
protected final List<DRowModel> rows;
protected final List<DOutputModel> outputs;

private final Map<String, Type> variableTypes;
private final org.kie.dmn.feel.runtime.decisiontables.DecisionTable dtable;
Expand Down Expand Up @@ -121,41 +126,80 @@ public boolean hasDefaultValues() {
return outputs.stream().allMatch( o -> o.compiledDefault != null );
}

private void initInputClauses( CompilerContext feelctx, Map<String, CompiledFEELExpression> compilationCache ) {
int index = 1;
protected void iterateOverRows(BiConsumer<DRowModel, Integer> rowsFeelExpressionGeneration) {
int rowIndex = 1;
for (DRowModel row : rows) {
rowsFeelExpressionGeneration.accept(row, rowIndex);
rowIndex++;
}
}

protected void initRows(CompilerContext feelctx, Map<String, CompiledFEELExpression> compilationCache) {
iterateOverRows((row, rowIndex) -> row.compiledOutputs = row.outputs.stream().map(expr -> compileFeelExpression(dt, feel, feelctx, Msg.ERR_COMPILING_FEEL_EXPR_ON_DT_RULE_IDX, compilationCache, expr, rowIndex)).collect(toList()));
}

protected ClassOrInterfaceDeclaration[][] generateRows(CompilerContext feelctx) {
List<ClassOrInterfaceDeclaration[]> allRows = new ArrayList<>();
iterateOverRows((row, integer) -> {
ClassOrInterfaceDeclaration[] rowCompiledOutputs = row.outputs.stream().map(expr -> feel.generateFeelExpressionSource(expr, feelctx)).toArray(ClassOrInterfaceDeclaration[]::new);
allRows.add(rowCompiledOutputs);
});
return allRows.toArray(new ClassOrInterfaceDeclaration[0][0]);
}

protected void iterateOverInputClauses(BiConsumer<DColumnModel, Integer> inputFeelExpressionGeneration) {
int index = 0;
for (DColumnModel column : columns) {
String inputValuesText = getInputValuesText( column.inputClause );
if (inputValuesText != null && !inputValuesText.isEmpty()) {
column.inputTests = feel.evaluateUnaryTests( inputValuesText, variableTypes );
}
column.compiledInputClause = compileFeelExpression( column.inputClause, feel, feelctx, Msg.ERR_COMPILING_FEEL_EXPR_ON_DT_INPUT_CLAUSE_IDX, compilationCache, column.getName(), index++ );
inputFeelExpressionGeneration.accept(column, index);
index++;
}
}

private void initRows( CompilerContext feelctx, Map<String, CompiledFEELExpression> compilationCache ) {
int index = 1;
for (DRowModel row : rows) {
int rowIndex = index;
row.compiledOutputs = row.outputs.stream().map( expr -> compileFeelExpression( dt, feel, feelctx, Msg.ERR_COMPILING_FEEL_EXPR_ON_DT_RULE_IDX, compilationCache, expr, rowIndex ) ).collect( toList() );
index++;
}
protected void initInputClauses(CompilerContext feelctx, Map<String, CompiledFEELExpression> compilationCache) {
iterateOverInputClauses((column, index) -> column.compiledInputClause = compileFeelExpression(column.inputClause, feel, feelctx, Msg.ERR_COMPILING_FEEL_EXPR_ON_DT_INPUT_CLAUSE_IDX, compilationCache, column.getName(), index));
}

private CompiledFEELExpression compileFeelExpression( DMNElement element, DMNFEELHelper feel, CompilerContext feelctx, Msg.Message msg, Map<String, CompiledFEELExpression> compilationCache, String expr, int index ) {
return compilationCache.computeIfAbsent(expr, e -> e == null || e.isEmpty() ? ctx -> null : (CompiledFEELExpression ) feel.compile( model, element, msg, dtName, e, feelctx, index ) );
protected List<ClassOrInterfaceDeclaration> generateInputClauses(CompilerContext feelctx) {
List<ClassOrInterfaceDeclaration> inputClauses = new ArrayList<>();
iterateOverInputClauses((column, index) -> inputClauses.add(feel.generateFeelExpressionSource(column.getName(), feelctx)));
return inputClauses;
}

private void initOutputClauses( CompilerContext feelctx, Map<String, CompiledFEELExpression> compilationCache ) {
protected void iterateOverOutputClauses(BiConsumer<DOutputModel, String> ouputFeelExpressionGeneration) {
for (DOutputModel output : outputs) {
output.outputValues = getOutputValuesTests( output );
String defaultValue = output.outputClause.getDefaultOutputEntry() != null ? output.outputClause.getDefaultOutputEntry().getText() : null;
if (defaultValue != null && !defaultValue.isEmpty()) {
output.compiledDefault = compileFeelExpression( output.outputClause, feel, feelctx, Msg.ERR_COMPILING_FEEL_EXPR_ON_DT_OUTPUT_CLAUSE_IDX, compilationCache, defaultValue, 0 );
ouputFeelExpressionGeneration.accept(output, defaultValue);
}
}
}

private List<UnaryTest> getOutputValuesTests( DOutputModel output ) {
protected void initOutputClauses( CompilerContext feelctx, Map<String, CompiledFEELExpression> compilationCache ) {
iterateOverOutputClauses((output, defaultValue) -> output.compiledDefault = compileFeelExpression(output.outputClause, feel, feelctx, Msg.ERR_COMPILING_FEEL_EXPR_ON_DT_OUTPUT_CLAUSE_IDX, compilationCache, defaultValue, 0));
}

public Map<String, ClassOrInterfaceDeclaration> generateOutputClauses(CompilerContext feelctx) {
Map<String, ClassOrInterfaceDeclaration> outputClauses = new HashMap<>();
iterateOverOutputClauses((output, defaultValue) -> outputClauses.put(defaultValue, feel.generateFeelExpressionSource(defaultValue, feelctx)));
return outputClauses;
}

protected CompiledFEELExpression compileFeelExpression( DMNElement element, DMNFEELHelper feel, CompilerContext feelctx, Msg.Message msg, Map<String, CompiledFEELExpression> compilationCache, String expr, int index ) {
return compilationCache.computeIfAbsent(expr, e -> {
if (e == null || e.isEmpty()) {
return ctx -> null;
} else {
return (CompiledFEELExpression) feel.compile(model, element, msg, dtName, e, feelctx, index);
}
});
}

protected List<UnaryTest> getOutputValuesTests( DOutputModel output ) {
String outputValuesText = Optional.ofNullable( output.outputClause.getOutputValues() ).map( UnaryTests::getText ).orElse( null );
output.typeRef = inferTypeRef(model, dt, output.outputClause);
if (outputValuesText != null && !outputValuesText.isEmpty()) {
Expand Down Expand Up @@ -224,9 +268,9 @@ public org.kie.dmn.feel.runtime.decisiontables.DecisionTable asDecisionTable() {
public static class DRowModel {

private final List<String> inputs;
private final List<String> outputs;
protected final List<String> outputs;

private List<CompiledFEELExpression> compiledOutputs;
protected List<CompiledFEELExpression> compiledOutputs;

DRowModel(DecisionRule dr) {
this.inputs = dr.getInputEntry().stream()
Expand All @@ -250,7 +294,7 @@ public static class DColumnModel {
private final Type type;

private List<UnaryTest> inputTests;
private CompiledFEELExpression compiledInputClause;
protected CompiledFEELExpression compiledInputClause;

DColumnModel(InputClause inputClause) {
this.inputClause = inputClause;
Expand Down Expand Up @@ -298,9 +342,9 @@ public String getGeneratedClassName(ExecModelDMNEvaluatorCompiler.GeneratorsEnum
}

public static class DOutputModel {
private final OutputClause outputClause;
private List<UnaryTest> outputValues;
private CompiledFEELExpression compiledDefault;
protected final OutputClause outputClause;
protected List<UnaryTest> outputValues;
protected CompiledFEELExpression compiledDefault;
private BaseDMNTypeImpl typeRef;

DOutputModel( OutputClause outputClause ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,27 @@ public ExecModelDMNClassLoaderCompiler(DMNCompilerImpl compiler, DMNRuleClassFil
@Override
protected DMNExpressionEvaluator compileDecisionTable(DMNCompilerContext ctx, DMNModelImpl model, DMNBaseNode node, String dtName, DecisionTable dt) {
String decisionName = ExecModelDMNEvaluatorCompiler.getDecisionTableName(dtName, dt);
// This is used just to get the compiled class name, but it only needs the namespace and the table name. exec model DTableModel is used instead
DTableModel dTableModel = new DTableModel(ctx.getFeelHelper(), model, dtName, decisionName, dt);
String className = dTableModel.getGeneratedClassName(ExecModelDMNEvaluatorCompiler.GeneratorsEnum.EVALUATOR);
Optional<String> generatedClass = dmnRuleClassFile.getCompiledClass(className);
String evaluatorClassName = dTableModel.getGeneratedClassName(ExecModelDMNEvaluatorCompiler.GeneratorsEnum.EVALUATOR);
Optional<String> generatedClass = dmnRuleClassFile.getCompiledClass(evaluatorClassName);

return generatedClass.map(gc -> {
try {
Class<?> clazz = getRootClassLoader().loadClass(gc);
AbstractModelEvaluator evaluatorInstance = (AbstractModelEvaluator) clazz.newInstance();
evaluatorInstance.initParameters(ctx, dTableModel, node);
Class<?> evaluatorClass = getRootClassLoader().loadClass(gc);
AbstractModelEvaluator evaluatorInstance = (AbstractModelEvaluator) evaluatorClass.newInstance();

logger.debug("Read compiled evaluator from class loader: " + className);
String feelExpressionClassName = dTableModel.getGeneratedClassName(ExecModelDMNEvaluatorCompiler.GeneratorsEnum.FEEL_EXPRESSION);
Class<?> feelExpressionClass = getRootClassLoader().loadClass(feelExpressionClassName);
DTableModel execModelDTableModel = new ExecModelDTableModel(ctx.getFeelHelper(), model, dtName, decisionName, dt, feelExpressionClass);

evaluatorInstance.initParameters(ctx, execModelDTableModel, node);

logger.debug("Read compiled evaluator from class loader: " + evaluatorClassName);
return evaluatorInstance;
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
throw new RuntimeException(e);
throw new RuntimeException("Cannot instantiate class" + e);
}
}).orElseThrow(() -> new RuntimeException("Cannot instantiate evaluator"));
}).orElseThrow(() -> new RuntimeException("No evaluator class found in file: " + dmnRuleClassFile));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@
import static org.drools.modelcompiler.builder.JavaParserCompiler.getCompiler;

public class ExecModelDMNEvaluatorCompiler extends DMNEvaluatorCompiler {

static final Logger logger = LoggerFactory.getLogger(ExecModelDMNEvaluatorCompiler.class);
private GeneratorsEnum[] GENERATORS_WITHOUT_EXPRESSIONS = new GeneratorsEnum[] {
GeneratorsEnum.EVALUATOR,
GeneratorsEnum.UNIT,
GeneratorsEnum.EXEC_MODEL,
GeneratorsEnum.UNARY_TESTS
};

public ExecModelDMNEvaluatorCompiler(DMNCompilerImpl compiler) {
super(compiler);
Expand All @@ -58,7 +65,8 @@ enum GeneratorsEnum {
EVALUATOR("Evaluator", new EvaluatorSourceGenerator()),
UNIT("DTUnit", new UnitSourceGenerator()),
EXEC_MODEL("ExecModel", new ExecModelSourceGenerator()),
UNARY_TESTS("UnaryTests", new UnaryTestsSourceGenerator());
UNARY_TESTS("UnaryTests", new UnaryTestsSourceGenerator()),
FEEL_EXPRESSION("FeelExpression", new FeelExpressionSourceGenerator());

String type;
SourceGenerator sourceGenerator;
Expand Down Expand Up @@ -110,7 +118,7 @@ public AbstractModelEvaluator generateEvaluator( DMNCompilerContext ctx, DTableM

MemoryFileSystem srcMfs = new MemoryFileSystem();
MemoryFileSystem trgMfs = new MemoryFileSystem();
String[] fileNames = new String[GeneratorsEnum.values().length];
String[] fileNames = new String[getGenerators().length];
List<GeneratedSource> generatedSources = new ArrayList<>();

generateSources(ctx, dTableModel, srcMfs, fileNames, generatedSources);
Expand All @@ -122,16 +130,20 @@ public AbstractModelEvaluator generateEvaluator( DMNCompilerContext ctx, DTableM

protected void generateSources(DMNCompilerContext ctx, DTableModel dTableModel, MemoryFileSystem srcMfs, String[] fileNames, List<GeneratedSource> generatedSources) {
for (int i = 0; i < fileNames.length; i++) {
GeneratorsEnum generator = GeneratorsEnum.values()[i];
GeneratorsEnum generator = getGenerators()[i];
String className = dTableModel.getGeneratedClassName(generator);
String fileName = "src/main/java/" + className.replace( '.', '/' ) + ".java";
String fileName = "src/main/java/" + className.replace('.', '/') + ".java";
String javaSource = generator.sourceGenerator.generate(ctx, ctx.getFeelHelper(), dTableModel);
fileNames[i] = fileName;
generatedSources.add(new GeneratedSource(fileName, javaSource));
srcMfs.write( fileNames[i], javaSource.getBytes() );
srcMfs.write(fileNames[i], javaSource.getBytes());
}
}

protected GeneratorsEnum[] getGenerators() {
return GENERATORS_WITHOUT_EXPRESSIONS;
}

private AbstractModelEvaluator createInvoker(String pkgName, String clasName) {
try {
Class<?> evalClass = projectClassLoader.loadClass(pkgName + "." + clasName + "Evaluator");
Expand Down Expand Up @@ -370,7 +382,7 @@ public String getUnaryTestsSource( DMNCompilerContext ctx, DMNFEELHelper feel, D
testClassesByInput.put(input, testClass);
instancesBuilder.append( " private static final CompiledDTTest " + testClass + "_INSTANCE = new CompiledDTTest( new " + testClass + "() );\n" );

String sourceCode = feel.compileUnaryTests(
String sourceCode = feel.generateUnaryTestsSource(
input,
ctx,
dTableModel.getColumns().get(j).getType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@
import org.kie.dmn.core.compiler.DMNCompilerImpl;
import org.kie.dmn.core.impl.DMNModelImpl;
import org.kie.dmn.model.api.DecisionTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExecModelDMNMavenSourceCompiler extends ExecModelDMNEvaluatorCompiler {
private GeneratorsEnum[] ALL_GENERATORS = new GeneratorsEnum[] {
GeneratorsEnum.EVALUATOR,
GeneratorsEnum.UNIT,
GeneratorsEnum.EXEC_MODEL,
GeneratorsEnum.UNARY_TESTS,
GeneratorsEnum.FEEL_EXPRESSION,

};

List<AfterGeneratingSourcesListener> afterGeneratingSourcesListeners = new ArrayList<>();

public void register(AfterGeneratingSourcesListener listener) {
afterGeneratingSourcesListeners.add(listener);
}

static final Logger logger = LoggerFactory.getLogger(ExecModelDMNMavenSourceCompiler.class);

public ExecModelDMNMavenSourceCompiler(DMNCompilerImpl compiler) {
super(compiler);
}
Expand All @@ -66,4 +71,9 @@ public AbstractModelEvaluator generateEvaluator( DMNCompilerContext ctx, DTableM

return null;
}

@Override
protected GeneratorsEnum[] getGenerators() {
return ALL_GENERATORS;
}
}
Loading

0 comments on commit d62a3b7

Please sign in to comment.