org.drools.compiler.rule.builder.dialect.DialectUtil Maven / Gradle / Ivy
/*
* Copyright 2015 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.
*
* 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.compiler.rule.builder.dialect;
import org.drools.compiler.builder.impl.KnowledgeBuilderImpl;
import org.drools.compiler.commons.jci.readers.ResourceReader;
import org.drools.compiler.compiler.BoundIdentifiers;
import org.drools.compiler.compiler.DescrBuildError;
import org.drools.compiler.lang.descr.BaseDescr;
import org.drools.compiler.lang.descr.FunctionDescr;
import org.drools.compiler.lang.descr.ImportDescr;
import org.drools.compiler.lang.descr.PackageDescr;
import org.drools.compiler.rule.builder.RuleBuildContext;
import org.drools.compiler.rule.builder.dialect.java.JavaAnalysisResult;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaCatchBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaContainerBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaElseBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaFinalBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaForBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaIfBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaInterfacePointsDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaLocalDeclarationDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaLocalDeclarationDescr.IdentifierDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaModifyBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaThrowBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaTryBlockDescr;
import org.drools.compiler.rule.builder.dialect.java.parser.JavaWhileBlockDescr;
import org.drools.compiler.rule.builder.dialect.mvel.MVELAnalysisResult;
import org.drools.compiler.rule.builder.dialect.mvel.MVELConsequenceBuilder;
import org.drools.compiler.rule.builder.dialect.mvel.MVELDialect;
import org.drools.core.factmodel.ClassDefinition;
import org.drools.core.reteoo.PropertySpecificUtil;
import org.drools.core.rule.ConsequenceMetaData;
import org.drools.core.rule.Declaration;
import org.drools.core.rule.TypeDeclaration;
import org.drools.core.spi.ClassWireable;
import org.drools.core.spi.KnowledgeHelper;
import org.drools.core.util.ClassUtils;
import org.drools.core.util.bitmask.AllSetBitMask;
import org.drools.core.util.bitmask.BitMask;
import org.kie.api.definition.type.FactField;
import org.mvel2.CompileException;
import org.mvel2.Macro;
import org.mvel2.MacroProcessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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 static org.drools.core.reteoo.PropertySpecificUtil.allSetButTraitBitMask;
import static org.drools.core.reteoo.PropertySpecificUtil.getEmptyPropertyReactiveMask;
import static org.drools.core.reteoo.PropertySpecificUtil.setPropertyOnMask;
import static org.drools.core.util.ClassUtils.*;
import static org.drools.core.util.StringUtils.*;
public final class DialectUtil {
private static final Pattern NON_ALPHA_REGEX = Pattern.compile("[\\W]");
private static final Pattern LINE_BREAK_FINDER = Pattern.compile( "\\r\\n|\\r|\\n" );
/**
* Takes a given name and makes sure that its legal and doesn't already exist. If the file exists it increases counter appender untill it is unique.
*/
public static String getUniqueLegalName(final String packageName,
final String name,
final int seed,
final String ext,
final String prefix,
final ResourceReader src) {
// replaces all non alphanumeric or $ chars with _
final String newName = prefix + "_" + normalizeRuleName( name );
if (ext.equals("java")) {
return newName + Math.abs(seed);
}
final String fileName = packageName.replace('.', '/') + "/" + newName;
if (src == null || !src.isAvailable(fileName + "." + ext)) return newName;
// make sure the class name does not exist, if it does increase the counter
int counter = -1;
while (true) {
counter++;
final String actualName = fileName + "_" + counter + "." + ext;
//MVEL:test null to Fix failing test on MVELConsequenceBuilderTest.testImperativeCodeError()
if (!src.isAvailable(actualName)) break;
}
// we have duplicate file names so append counter
return newName + "_" + counter;
}
public static String fixBlockDescr(final RuleBuildContext context,
final JavaAnalysisResult analysis,
Map decls) {
// This is a list of all the non container blocks, which initially are in tree form.
List blocks = buildBlockDescrs(new ArrayList(), analysis.getBlockDescrs());
return fixBlockDescr(context, analysis, decls, blocks);
}
public static String fixBlockDescr(final RuleBuildContext context,
final JavaAnalysisResult analysis,
Map decls,
List blocks) {
MVELDialect mvel = (MVELDialect) context.getDialect("mvel");
String originalCode = analysis.getAnalyzedExpr();
BoundIdentifiers bindings = analysis.getBoundIdentifiers();
// sorting exit points for correct order iteration
Collections.sort(blocks,
new Comparator() {
public int compare(JavaBlockDescr o1,
JavaBlockDescr o2) {
return o1.getStart() - o2.getStart();
}
});
StringBuilder consequence = new StringBuilder();
int lastAdded = 0;
for (JavaBlockDescr block : blocks) {
if (block.getEnd() == 0 || block.getEnd() > originalCode.length() ) {
// do nothing, it was incorrectly parsed, but this error should be picked up else where
continue;
}
// adding chunk
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1));
lastAdded = block.getEnd();
switch (block.getType()) {
case MODIFY:
case UPDATE:
case DELETE:
rewriteDescr(context,
originalCode,
mvel,
consequence,
block,
bindings,
decls);
break;
case ENTRY:
case EXIT:
case CHANNEL:
rewriteInterfacePoint(context,
originalCode,
consequence,
(JavaInterfacePointsDescr) block);
break;
case INSERT:
parseInsertDescr(context, block);
default:
consequence.append(originalCode.substring(block.getStart() - 1, lastAdded));
}
}
consequence.append(originalCode.substring(lastAdded));
return consequence.toString();
}
private static List buildBlockDescrs(List descrs,
JavaContainerBlockDescr parentBlock) {
for (JavaBlockDescr block : parentBlock.getJavaBlockDescrs()) {
if (block instanceof JavaContainerBlockDescr) {
buildBlockDescrs(descrs, (JavaContainerBlockDescr) block);
} else {
descrs.add(block);
}
}
return descrs;
}
/**
* This code is not currently used, it's commented out in method caller. This is because we couldn't
* get this to work and will have to wait until MVEL supports genercs (mdp).
*/
public static void setContainerBlockInputs(RuleBuildContext context,
List descrs,
JavaContainerBlockDescr parentBlock,
String originalCode,
BoundIdentifiers bindings,
Map> parentVars,
int offset) {
StringBuilder consequence = new StringBuilder();
int lastAdded = 0;
// strip blocks, so we can analyse this block with MVEL
for (JavaBlockDescr block : parentBlock.getJavaBlockDescrs()) {
if (block.getEnd() == 0) {
// do nothing, it was incorrectly parsed, but this error should be picked up else where
continue;
}
if (block.getType() == JavaBlockDescr.BlockType.TRY) {
// adding previous chunk up to the start of this block
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1 - offset));
JavaTryBlockDescr tryDescr = (JavaTryBlockDescr) block;
if (tryDescr.getFinal() != null) {
lastAdded = tryDescr.getFinal().getEnd() - offset;
} else {
lastAdded = tryDescr.getCatches().get(tryDescr.getCatches().size() - 1).getEnd() - offset;
}
stripTryDescr(originalCode,
consequence,
(JavaTryBlockDescr) block,
offset);
} else if (block.getType() == JavaBlockDescr.BlockType.THROW) {
// adding previous chunk up to the start of this block
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1 - offset));
JavaThrowBlockDescr throwBlock = (JavaThrowBlockDescr) block;
addWhiteSpaces(originalCode, consequence, throwBlock.getStart() - offset, throwBlock.getTextStart() - offset);
consequence.append(originalCode.substring(throwBlock.getTextStart() - offset - 1, throwBlock.getEnd() - 1 - offset)).append(";");
lastAdded = throwBlock.getEnd() - offset;
} else if (block.getType() == JavaBlockDescr.BlockType.IF) {
// adding previous chunk up to the start of this block
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1 - offset));
JavaIfBlockDescr ifDescr = (JavaIfBlockDescr) block;
lastAdded = ifDescr.getEnd() - offset;
stripBlockDescr(originalCode,
consequence,
ifDescr,
offset);
} else if (block.getType() == JavaBlockDescr.BlockType.ELSE) {
// adding previous chunk up to the start of this block
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1 - offset));
JavaElseBlockDescr elseDescr = (JavaElseBlockDescr) block;
lastAdded = elseDescr.getEnd() - offset;
stripBlockDescr(originalCode,
consequence,
elseDescr,
offset);
} else if (block.getType() == JavaBlockDescr.BlockType.WHILE) {
// adding previous chunk up to the start of this block
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1 - offset));
JavaWhileBlockDescr whileDescr = (JavaWhileBlockDescr) block;
lastAdded = whileDescr.getEnd() - offset;
stripBlockDescr(originalCode,
consequence,
whileDescr,
offset);
} else if (block.getType() == JavaBlockDescr.BlockType.FOR) {
// adding previous chunk up to the start of this block
consequence.append(originalCode.substring(lastAdded,
block.getStart() - 1 - offset));
JavaForBlockDescr forDescr = (JavaForBlockDescr) block;
lastAdded = forDescr.getEnd() - offset;
stripBlockDescr(originalCode,
consequence,
forDescr,
offset);
}
}
consequence.append(originalCode.substring(lastAdded));
// We need to do this as MVEL doesn't recognise "modify"
MacroProcessor macroProcessor = new MacroProcessor();
Map macros = new HashMap(MVELConsequenceBuilder.macros);
macros.put("modify",
new Macro() {
public String doMacro() {
return "with ";
}
});
macroProcessor.setMacros(macros);
String mvelCode = macroProcessor.parse(consequence.toString());
Map> inputs = getInputs(context, mvelCode, bindings, parentVars);
inputs.putAll(parentVars);
parentBlock.setInputs(inputs);
// now go depth, set inputs for each nested container
// set inputs for current container blocks to be rewritten
for (JavaBlockDescr block : parentBlock.getJavaBlockDescrs()) {
if (block.getType() == JavaBlockDescr.BlockType.TRY) {
JavaTryBlockDescr tryBlock = (JavaTryBlockDescr) block;
setContainerBlockInputs(context,
descrs,
tryBlock,
originalCode.substring(tryBlock.getTextStart() - offset, tryBlock.getEnd() - 1 - offset),
bindings,
inputs,
tryBlock.getTextStart());
for (JavaCatchBlockDescr catchBlock : tryBlock.getCatches()) {
setContainerBlockInputs(context,
descrs,
catchBlock,
catchBlock.getClause() + "=null;" + originalCode.substring(catchBlock.getTextStart() - offset, catchBlock.getEnd() - 1 - offset),
bindings,
inputs,
tryBlock.getTextStart());
}
if (tryBlock.getFinal() != null) {
JavaFinalBlockDescr finalBlock = tryBlock.getFinal();
setContainerBlockInputs(context,
descrs,
finalBlock,
originalCode.substring(finalBlock.getTextStart() - offset, finalBlock.getEnd() - 1 - offset),
bindings,
inputs,
tryBlock.getTextStart());
}
} else if (block.getType() == JavaBlockDescr.BlockType.IF) {
JavaIfBlockDescr ifBlock = (JavaIfBlockDescr) block;
int adjustBlock = (originalCode.charAt(ifBlock.getTextStart() - offset - 1) == '{') ? 0 : 1;
setContainerBlockInputs(context,
descrs,
ifBlock,
originalCode.substring(ifBlock.getTextStart() - offset + adjustBlock, ifBlock.getEnd() - 1 - offset - adjustBlock),
bindings,
inputs,
ifBlock.getTextStart());
} else if (block.getType() == JavaBlockDescr.BlockType.ELSE) {
JavaElseBlockDescr elseBlock = (JavaElseBlockDescr) block;
int adjustBlock = (originalCode.charAt(elseBlock.getTextStart() - offset - 1) == '{') ? 0 : 1;
setContainerBlockInputs(context,
descrs,
elseBlock,
originalCode.substring(elseBlock.getTextStart() - offset + adjustBlock, elseBlock.getEnd() - 1 - offset - adjustBlock),
bindings,
inputs,
elseBlock.getTextStart());
} else if (block.getType() == JavaBlockDescr.BlockType.WHILE) {
JavaWhileBlockDescr whileBlock = (JavaWhileBlockDescr) block;
int adjustBlock = (originalCode.charAt(whileBlock.getTextStart() - offset - 1) == '{') ? 0 : 1;
setContainerBlockInputs(context,
descrs,
whileBlock,
originalCode.substring(whileBlock.getTextStart() - offset + adjustBlock, whileBlock.getEnd() - 1 - offset - adjustBlock),
bindings,
inputs,
whileBlock.getTextStart());
} else if (block.getType() == JavaBlockDescr.BlockType.FOR) {
JavaForBlockDescr forBlock = (JavaForBlockDescr) block;
int adjustBlock = (originalCode.charAt(forBlock.getTextStart() - offset - 1) == '{') ? 0 : 1;
setContainerBlockInputs(context,
descrs,
forBlock,
originalCode.substring(forBlock.getStartParen() - offset, forBlock.getInitEnd() - offset) +
originalCode.substring(forBlock.getTextStart() - offset + adjustBlock, forBlock.getEnd() - 1 - offset - adjustBlock),
bindings,
inputs,
forBlock.getTextStart() - (forBlock.getInitEnd() - forBlock.getStartParen()));
} else {
block.setInputs(inputs); // each block to be rewritten now knows it's own variables
descrs.add(block);
}
}
}
private static Map> getInputs(final RuleBuildContext context,
String code,
BoundIdentifiers bindings,
Map> parentVars) {
MVELDialect mvel = (MVELDialect) context.getDialect("mvel");
MVELAnalysisResult mvelAnalysis = null;
try {
mvelAnalysis = (MVELAnalysisResult) mvel.analyzeBlock(context,
code,
bindings,
parentVars,
"drools",
KnowledgeHelper.class);
} catch (Exception e) {
// swallow this as the error will be reported else where
}
return (mvelAnalysis != null) ? mvelAnalysis.getMvelVariables() : new HashMap>();
}
private static void addWhiteSpaces(String original, StringBuilder consequence, int start, int end) {
for (int i = start; i < end; i++) {
switch (original.charAt(i)) {
case '\n':
case '\r':
case '\t':
case ' ':
consequence.append(original.charAt(i));
break;
default:
consequence.append(" ");
}
}
}
private static void stripTryDescr(String originalCode,
StringBuilder consequence,
JavaTryBlockDescr block,
int offset) {
addWhiteSpaces(originalCode, consequence, consequence.length(), block.getTextStart() - offset);
addWhiteSpaces(originalCode, consequence, consequence.length(), block.getEnd() - offset);
for (JavaCatchBlockDescr catchBlock : block.getCatches()) {
addWhiteSpaces(originalCode, consequence, consequence.length(),
catchBlock.getTextStart() - offset);
addWhiteSpaces(originalCode, consequence, consequence.length(),
catchBlock.getEnd() - offset);
}
if (block.getFinal() != null) {
addWhiteSpaces(originalCode, consequence, consequence.length(), block.getFinal().getTextStart() - offset);
addWhiteSpaces(originalCode, consequence, consequence.length(), block.getFinal().getEnd() - offset);
}
}
private static void stripBlockDescr(String originalCode,
StringBuilder consequence,
JavaBlockDescr block,
int offset) {
addWhiteSpaces(originalCode, consequence, consequence.length(), block.getEnd() - offset);
}
@SuppressWarnings("unchecked")
private static void rewriteInterfacePoint(final RuleBuildContext context,
final String originalCode,
final StringBuilder consequence,
final JavaInterfacePointsDescr ep) {
// rewriting it for proper exitPoints access
consequence.append("drools.get");
if (ep.getType() == JavaBlockDescr.BlockType.EXIT) {
consequence.append("ExitPoint( ");
} else if (ep.getType() == JavaBlockDescr.BlockType.ENTRY) {
consequence.append("EntryPoint( ");
} else if (ep.getType() == JavaBlockDescr.BlockType.CHANNEL) {
consequence.append("Channel( ");
} else {
context.addError(new DescrBuildError(context.getParentDescr(),
context.getRuleDescr(),
ep,
"Unable to rewrite code block: " + ep + "\n"));
return;
}
consequence.append(ep.getId());
consequence.append(" )");
// the following is a hack to preserve line breaks.
String originalBlock = originalCode.substring(ep.getStart() - 1,
ep.getEnd());
int end = originalBlock.indexOf("]");
addLineBreaks(consequence,
originalBlock.substring(0,
end));
}
private static boolean rewriteDescr(final RuleBuildContext context,
final String originalCode,
final MVELDialect mvel,
final StringBuilder consequence,
final JavaBlockDescr d,
final BoundIdentifiers bindings,
final Map decls) {
if ( d.getEnd() == 0 ) {
// do nothing, it was incorrectly parsed, but this error should be picked up else where
return false;
}
boolean typeSafety = context.isTypesafe();
context.setTypesafe( false ); // we have to analyse in dynamic mode for now, as we cannot safely determine all input vars
Map> localTypes = d.getInputs();
if( d.getInScopeLocalVars() != null && ! d.getInScopeLocalVars().isEmpty() ) {
localTypes = new HashMap>( d.getInputs() != null ? d.getInputs() : Collections.EMPTY_MAP );
for( JavaLocalDeclarationDescr local : d.getInScopeLocalVars() ) {
// these are variables declared in the code itself that are in the scope for this expression
try {
Class> type = context.getDialect( "java" ).getPackageRegistry().getTypeResolver().resolveType( local.getRawType() );
for( IdentifierDescr id : local.getIdentifiers() ) {
localTypes.put( id.getIdentifier(), type );
}
} catch ( ClassNotFoundException e ) {
context.addError(new DescrBuildError(context.getRuleDescr(),
context.getParentDescr(),
null,
"Unable to resolve type " + local.getRawType() + ":\n" + e.getMessage()));
}
}
}
MVELAnalysisResult mvelAnalysis = ( MVELAnalysisResult ) mvel.analyzeBlock( context,
d.getTargetExpression(),
bindings,
localTypes,
"drools",
KnowledgeHelper.class);
context.setTypesafe( typeSafety );
if ( mvelAnalysis == null ) {
// something bad happened, issue already logged in errors
return false;
}
Class ret = mvelAnalysis.getReturnType();
if ( ret == null ) {
// not possible to evaluate expression return value
context.addError(new DescrBuildError(context.getParentDescr(),
context.getRuleDescr(),
originalCode,
"Unable to determine the resulting type of the expression: " + d.getTargetExpression() + "\n"));
return false;
}
// adding modify expression
String retString = ClassUtils.canonicalName( ret );
String declrString;
if (d.getTargetExpression().charAt( 0 ) == '(' ) {
declrString = d.getTargetExpression().substring( 1,d.getTargetExpression().length() -1 ).trim();
} else {
declrString = d.getTargetExpression();
}
String obj = declrString;
Declaration declr = decls.get( declrString );
consequence.append( "{ " );
if ( declr == null ) {
obj = "__obj__";
consequence.append( retString );
consequence.append( " " );
consequence.append( obj);
consequence.append( " = " );
consequence.append( d.getTargetExpression() );
consequence.append( "; " );
}
if ( declr == null || declr.isInternalFact() ) {
consequence.append( "org.kie.api.runtime.rule.FactHandle " );
consequence.append( obj );
consequence.append( "__Handle2__ = drools.getFactHandle(" );
consequence.append( obj );
consequence.append( ");" );
}
// the following is a hack to preserve line breaks.
String originalBlock = originalCode.substring( d.getStart() - 1, d.getEnd() );
switch (d.getType()) {
case MODIFY:
rewriteModifyDescr(context, d, originalBlock, consequence, declr, obj);
break;
case UPDATE:
rewriteUpdateDescr(context, d, consequence, declr, obj);
break;
case DELETE:
rewriteDeleteDescr( context, d, consequence, declr, obj );
break;
}
return declr != null;
}
private static void rewriteModifyDescr( RuleBuildContext context,
JavaBlockDescr d,
String originalBlock,
StringBuilder consequence,
Declaration declr,
String obj ) {
List settableProperties = null;
Class> typeClass = findModifiedClass(context, d, declr);
TypeDeclaration typeDeclaration = typeClass == null ? null : context.getKnowledgeBuilder().getTypeDeclaration(typeClass);
boolean isPropertyReactive = typeDeclaration != null && typeDeclaration.isPropertyReactive();
if (isPropertyReactive) {
typeDeclaration.setTypeClass(typeClass);
settableProperties = typeDeclaration.getAccessibleProperties();
}
ConsequenceMetaData.Statement statement = null;
if (typeDeclaration != null) {
statement = new ConsequenceMetaData.Statement(ConsequenceMetaData.Statement.Type.MODIFY, typeClass);
context.getRule().getConsequenceMetaData().addStatement(statement);
}
BitMask modificationMask = isPropertyReactive ? getEmptyPropertyReactiveMask(settableProperties.size()) : allSetButTraitBitMask();
int end = originalBlock.indexOf("{");
if (end == -1) {
// no block
context.addError(new DescrBuildError(context.getParentDescr(),
context.getRuleDescr(),
null,
"Block missing after modify" + d.getTargetExpression() + " ?\n"));
return;
}
addLineBreaks(consequence, originalBlock.substring(0, end));
int start = end + 1;
// adding each of the expressions:
for (String exprStr : ((JavaModifyBlockDescr) d).getExpressions()) {
end = originalBlock.indexOf(exprStr, start);
addLineBreaks(consequence, originalBlock.substring(start, end));
consequence.append(obj).append(".");
consequence.append(exprStr);
consequence.append("; ");
start = end + exprStr.length();
if (typeDeclaration != null) {
modificationMask = parseModifiedProperties(statement, settableProperties, typeDeclaration, isPropertyReactive, modificationMask, exprStr);
}
}
addLineBreaks(consequence, originalBlock.substring(end));
appendUpdateStatement(consequence, declr, obj, modificationMask, typeClass);
}
private static void rewriteUpdateDescr( RuleBuildContext context,
JavaBlockDescr d,
StringBuilder consequence,
Declaration declr,
String obj) {
BitMask modificationMask = AllSetBitMask.get();
Class> typeClass = findModifiedClass(context, d, declr);
TypeDeclaration typeDeclaration = typeClass == null ? null : context.getKnowledgeBuilder().getTypeDeclaration(typeClass);
if (typeDeclaration != null) {
boolean isPropertyReactive = typeDeclaration.isPropertyReactive();
List settableProperties = null;
if (isPropertyReactive) {
typeDeclaration.setTypeClass(typeClass);
settableProperties = typeDeclaration.getAccessibleProperties();
modificationMask = getEmptyPropertyReactiveMask(settableProperties.size());
}
ConsequenceMetaData.Statement statement = new ConsequenceMetaData.Statement(ConsequenceMetaData.Statement.Type.MODIFY, typeClass);
context.getRule().getConsequenceMetaData().addStatement(statement);
if (isPropertyReactive) {
boolean parsedExprOnce = false;
// a late optimization to include this for-loop within this if
for (String expr : splitStatements(consequence)) {
String updateExpr = expr.replaceFirst("^\\Q" + obj + "\\E\\s*\\.", "");
if (!updateExpr.equals(expr)) {
parsedExprOnce = true;
modificationMask = parseModifiedProperties(statement, settableProperties, typeDeclaration, isPropertyReactive, modificationMask, updateExpr);
if ( modificationMask == allSetButTraitBitMask() ) {
// opt: if we were unable to detect the property in the mask is all set, so avoid the rest of the cycle
break;
}
}
}
if ( !parsedExprOnce ) {
// never called parseModifiedProperties(), hence never had the opportunity to "miss" the property and set mask to All-set; doing so here:
modificationMask = allSetButTraitBitMask();
}
}
}
appendUpdateStatement(consequence, declr, obj, modificationMask, typeClass);
}
private static void appendUpdateStatement(StringBuilder consequence, Declaration declr, String obj, BitMask modificationMask, Class> typeClass) {
boolean isInternalFact = declr == null || declr.isInternalFact();
consequence
.append("drools.update( ")
.append(obj)
.append(isInternalFact ? "__Handle2__, " : "__Handle__, ")
.append(modificationMask.getInstancingStatement())
.append(", ")
.append(typeClass != null ? typeClass.getCanonicalName() : "java.lang.Object")
.append(".class")
.append(" ); }");
}
private static BitMask parseModifiedProperties( ConsequenceMetaData.Statement statement,
List settableProperties,
TypeDeclaration typeDeclaration,
boolean propertyReactive,
BitMask modificationMask,
String exprStr) {
int endMethodName = exprStr.indexOf('(');
if (endMethodName >= 0) {
String methodName = exprStr.substring(0, endMethodName).trim();
String propertyName = setter2property(methodName);
int endMethodArgs = findEndOfMethodArgsIndex(exprStr, endMethodName);
String methodParams = exprStr.substring(endMethodName+1, endMethodArgs).trim();
List args = splitArgumentsList(methodParams);
int argsNr = args.size();
if (propertyName == null && exprStr.length() > endMethodArgs+1 && exprStr.substring(endMethodArgs+1).trim().startsWith(".")) {
propertyName = getter2property(methodName);
}
if (propertyName != null) {
modificationMask = updateModificationMask(settableProperties, propertyReactive, modificationMask, propertyName);
statement.addField(propertyName, argsNr > 0 ? args.get(0) : null);
}
List modifiedProps = typeDeclaration.getTypeClassDef().getModifiedPropsByMethod( methodName, argsNr );
if (modifiedProps != null) {
for (String modifiedProp : modifiedProps) {
modificationMask = updateModificationMask(settableProperties, propertyReactive, modificationMask, modifiedProp);
statement.addField(modifiedProp, argsNr > 0 ? args.get(0) : null);
}
}
if ( propertyReactive && propertyName == null && modifiedProps == null ) {
// I'm property reactive, but I was unable to infer which properties was modified, setting all bit in bitmask
modificationMask = allSetButTraitBitMask();
}
} else {
String propertyName = extractFirstIdentifier(exprStr, 0);
modificationMask = updateModificationMask(settableProperties, propertyReactive, modificationMask, propertyName);
int equalPos = exprStr.indexOf('=');
if (equalPos >= 0) {
String value = exprStr.substring(equalPos+1).trim();
statement.addField(propertyName, value);
}
}
return modificationMask;
}
private static BitMask updateModificationMask( List settableProperties,
boolean propertyReactive,
BitMask modificationMask,
String propertyName) {
if (propertyReactive) {
int index = settableProperties.indexOf(propertyName);
if (index >= 0) {
modificationMask = setPropertyOnMask(modificationMask, index);
}
}
return modificationMask;
}
private static Class> findModifiedClass(RuleBuildContext context, JavaBlockDescr d, Declaration declr) {
if (declr != null) {
return declr.getDeclarationClass();
}
String targetId = d.getTargetExpression().trim();
while (targetId.charAt(0) == '(' && targetId.charAt(targetId.length()-1) == ')') {
targetId = targetId.substring(1, targetId.length()-1).trim();
}
if (targetId.charAt(0) == '(') {
int endCast = targetId.indexOf(')');
if (endCast > 0) {
String castName = targetId.substring(1, endCast).trim();
Class> cast = findClassByName(context, castName);
if (cast != null) {
return cast;
}
targetId = targetId.substring(endCast+1).trim();
}
}
return targetId.contains("(") ? findFunctionReturnedClass(context, targetId) : findDeclarationClass(context, d, targetId);
}
private static Class> findDeclarationClass(RuleBuildContext context, JavaBlockDescr d, String statement) {
Class> inputClass = d.getInputs() == null ? null : d.getInputs().get(statement);
if (inputClass != null) {
return inputClass;
}
List localDeclarationDescrs = d.getInScopeLocalVars();
if (localDeclarationDescrs == null) {
return null;
}
String className = null;
for (JavaLocalDeclarationDescr localDeclr : localDeclarationDescrs) {
for (IdentifierDescr idDescr : localDeclr.getIdentifiers()) {
if (statement.equals(idDescr.getIdentifier())) {
className = localDeclr.getType();
break;
}
}
if (className != null) {
break;
}
}
return findClassByName(context, className);
}
public static Class> findClassByName(RuleBuildContext context, String className) {
if (className == null) {
return null;
}
String namespace = context.getRuleDescr().getNamespace();
KnowledgeBuilderImpl packageBuilder = context.getKnowledgeBuilder();
Class> clazz = null;
try {
clazz = Class.forName(className.indexOf('.') < 0 ? namespace + "." + className : className, false, packageBuilder.getRootClassLoader());
} catch (ClassNotFoundException e) { }
if (clazz != null) {
return clazz;
}
Set imports = new HashSet();
List pkgDescrs = packageBuilder.getPackageDescrs(namespace);
if (pkgDescrs == null) {
return null;
}
for (PackageDescr pkgDescr : pkgDescrs) {
for (ImportDescr importDescr : pkgDescr.getImports()) {
imports.add(importDescr.getTarget());
}
}
return findClass(className, imports, packageBuilder.getRootClassLoader());
}
private static Class> findFunctionReturnedClass(RuleBuildContext context, String statement) {
String functionName = statement.substring(0, statement.indexOf('('));
FunctionDescr function = lookupFunction(context, functionName);
return function == null ? null : findClassByName(context, function.getReturnType());
}
private static boolean rewriteDeleteDescr( RuleBuildContext context,
JavaBlockDescr d,
StringBuilder consequence,
Declaration declr,
String obj ) {
Class> typeClass = findModifiedClass(context, d, declr);
if (typeClass != null) {
ConsequenceMetaData.Statement statement = new ConsequenceMetaData.Statement(ConsequenceMetaData.Statement.Type.RETRACT, typeClass);
context.getRule().getConsequenceMetaData().addStatement(statement);
}
if (declr != null && !declr.isInternalFact()) {
consequence.append("drools.delete( ").append(obj).append("__Handle__ ); }");
} else {
consequence.append("drools.delete( ").append(obj).append("__Handle2__ ); }");
}
return declr != null;
}
private static void parseInsertDescr(RuleBuildContext context, JavaBlockDescr block) {
String expr = block.getTargetExpression();
if (expr.startsWith("new ")) {
int argsStart = expr.indexOf('(');
if (argsStart > 0) {
String className = expr.substring(4, argsStart).trim();
Class> typeClass = findClassByName(context, className);
TypeDeclaration typeDeclaration = typeClass == null ? null : context.getKnowledgeBuilder().getTypeDeclaration(typeClass);
if (typeDeclaration != null) {
ConsequenceMetaData.Statement statement = new ConsequenceMetaData.Statement(ConsequenceMetaData.Statement.Type.INSERT, typeClass);
context.getRule().getConsequenceMetaData().addStatement(statement);
String constructorParams = expr.substring(argsStart+1, expr.indexOf(')')).trim();
List args = splitArgumentsList(constructorParams);
ClassDefinition classDefinition = typeDeclaration.getTypeClassDef();
List fields = classDefinition.getFields();
if (args.size() == fields.size()) {
for (int i = 0; i < args.size(); i++) {
statement.addField(fields.get(i).getName(), args.get(i));
}
}
}
}
}
}
private static void addLineBreaks(StringBuilder consequence,
String chunk) {
Matcher m = LINE_BREAK_FINDER.matcher(chunk);
while (m.find()) {
consequence.append("\n");
}
}
public static void copyErrorLocation(Exception e, BaseDescr descr) {
if (e instanceof CompileException) {
CompileException compileException = (CompileException)e;
compileException.setLineNumber(descr.getLine());
compileException.setColumn(descr.getColumn());
}
}
private static FunctionDescr lookupFunction(RuleBuildContext context, String functionName) {
String packageName = context.getRule().getPackageName();
List pkgDescrs = context.getKnowledgeBuilder().getPackageDescrs(packageName);
for (PackageDescr pkgDescr : pkgDescrs) {
for (FunctionDescr function : pkgDescr.getFunctions()) {
if (function.getName().equals(functionName)) {
return function;
}
}
}
return null;
}
static String normalizeRuleName(String name) {
String normalized = name.replace(' ', '_');
if (!NON_ALPHA_REGEX.matcher(normalized).find()) {
return normalized;
}
StringBuilder sb = new StringBuilder(normalized.length());
for (char ch : normalized.toCharArray()) {
if (ch == '$') {
sb.append("_dollar_");
} else if (Character.isJavaIdentifierPart(ch)) {
sb.append(ch);
} else {
sb.append("$u").append((int)ch).append("$");
}
}
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy