/*
* 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.workbench.models.commons.backend.rule;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.math.NumberUtils;
import org.drools.core.util.DateUtils;
import org.drools.workbench.models.datamodel.oracle.DataType;
import org.drools.workbench.models.datamodel.oracle.MethodInfo;
import org.drools.workbench.models.datamodel.oracle.PackageDataModelOracle;
import org.drools.workbench.models.datamodel.rule.ActionCallMethod;
import org.drools.workbench.models.datamodel.rule.ActionFieldFunction;
import org.drools.workbench.models.datamodel.rule.FieldNatureType;
import org.drools.workbench.models.datamodel.rule.RuleModel;
import static org.drools.workbench.models.commons.backend.rule.RuleModelPersistenceHelper.*;
public class ActionCallMethodBuilder {
private RuleModel model;
private PackageDataModelOracle dmo;
private boolean isJavaDialect;
private Map<String, String> boundParams;
private String methodName;
private String variable;
private String[] parameters;
private int index;
public ActionCallMethodBuilder( RuleModel model,
PackageDataModelOracle dmo,
boolean isJavaDialect,
Map<String, String> boundParams ) {
this.model = model;
this.dmo = dmo;
this.isJavaDialect = isJavaDialect;
this.boundParams = boundParams;
}
//ActionCallMethods do not support chained method invocations
public boolean supports( final String line ) {
final List<String> splits = new ArrayList<String>();
int depth = 0;
int textDepth = 0;
boolean escape = false;
StringBuffer split = new StringBuffer();
for ( char c : line.toCharArray() ) {
if ( depth == 0 && c == '.' ) {
splits.add( split.toString() );
split = new StringBuffer();
depth = 0;
textDepth = 0;
escape = false;
continue;
} else if ( c == '\\' ) {
escape = true;
split.append( c );
continue;
} else if ( textDepth == 0 && c == '"' ) {
textDepth++;
} else if ( !escape && textDepth > 0 && c == '"' ) {
textDepth--;
} else if ( textDepth == 0 && c == '(' ) {
depth++;
} else if ( textDepth == 0 && c == ')' ) {
depth--;
}
split.append( c );
escape = false;
}
splits.add( split.toString() );
return splits.size() == 2;
}
public ActionCallMethod get( String variable,
String methodName,
String[] parameters ) {
this.variable = variable;
this.methodName = methodName;
this.parameters = parameters;
ActionCallMethod actionCallMethod = new ActionCallMethod();
actionCallMethod.setMethodName( methodName );
actionCallMethod.setVariable( variable );
actionCallMethod.setState( ActionCallMethod.TYPE_DEFINED );
for ( ActionFieldFunction parameter : getActionFieldFunctions() ) {
actionCallMethod.addFieldValue( parameter );
}
return actionCallMethod;
}
private List<ActionFieldFunction> getActionFieldFunctions() {
List<ActionFieldFunction> actionFieldFunctions = new ArrayList<ActionFieldFunction>();
this.index = 0;
for ( String param : parameters ) {
param = param.trim();
if ( param.length() == 0 ) {
continue;
}
actionFieldFunctions.add( getActionFieldFunction( param,
getDataType( param ) ) );
}
return actionFieldFunctions;
}
private ActionFieldFunction getActionFieldFunction( String param,
String dataType ) {
param = removeNumericSuffix( param,
dataType );
final int fieldNature = inferFieldNature( dataType,
param,
boundParams,
isJavaDialect );
//If the field is a formula don't adjust the param value
String paramValue = param;
switch ( fieldNature ) {
case FieldNatureType.TYPE_FORMULA:
break;
case FieldNatureType.TYPE_VARIABLE:
break;
case FieldNatureType.TYPE_TEMPLATE:
paramValue = unwrapTemplateKey( param );
break;
default:
paramValue = adjustParam( dataType,
param,
boundParams,
isJavaDialect );
}
ActionFieldFunction actionField = new ActionFieldFunction( methodName,
paramValue,
dataType );
actionField.setNature( fieldNature );
return actionField;
}
private String getDataType( String param ) {
String dataType;
MethodInfo methodInfo = getMethodInfo();
if ( methodInfo != null ) {
dataType = methodInfo.getParams().get( index++ );
} else {
dataType = boundParams.get( param );
}
if ( dataType == null ) {
dataType = inferDataType( param,
boundParams,
isJavaDialect );
}
return dataType;
}
private MethodInfo getMethodInfo() {
String variableType = boundParams.get( variable );
if ( variableType != null ) {
List<MethodInfo> methods = getMethodInfosForType( model,
dmo,
variableType );
if ( methods != null ) {
ArrayList<MethodInfo> methodInfos = getMethodInfos( methodName, methods );
if ( methodInfos.size() > 1 ) {
// Now if there were more than one method with the same name
// we need to start figuring out what is the correct one.
for ( MethodInfo methodInfo : methodInfos ) {
if ( compareParameters( methodInfo.getParams() ) ) {
return methodInfo;
}
}
} else if ( !methodInfos.isEmpty() ) {
// Not perfect, but works on most cases.
// There is no check if the parameter types match.
return methodInfos.get( 0 );
}
}
}
return null;
}
private ArrayList<MethodInfo> getMethodInfos( String methodName,
List<MethodInfo> methods ) {
ArrayList<MethodInfo> result = new ArrayList<MethodInfo>();
for ( MethodInfo method : methods ) {
if ( method.getName().equals( methodName ) ) {
result.add( method );
}
}
return result;
}
private boolean compareParameters( List<String> methodParams ) {
if ( methodParams.size() != parameters.length ) {
return false;
} else {
for ( int index = 0; index < methodParams.size(); index++ ) {
final String methodParamDataType = methodParams.get( index );
final String paramDataType = assertParamDataType( methodParamDataType,
parameters[ index ].trim() );
if ( !methodParamDataType.equals( paramDataType ) ) {
return false;
}
}
return true;
}
}
private String assertParamDataType( final String methodParamDataType,
final String paramValue ) {
if ( boundParams.containsKey( paramValue ) ) {
//If the parameter is a bound variable use the MethodInfo data-type
return methodParamDataType;
} else {
//Otherwise try coercing the parameter value into the method data-type until a match is found
if ( DataType.TYPE_BOOLEAN.equals( methodParamDataType ) ) {
if ( Boolean.TRUE.equals( Boolean.parseBoolean( paramValue ) ) || Boolean.FALSE.equals( Boolean.parseBoolean( paramValue ) ) ) {
return methodParamDataType;
}
return null;
} else if ( DataType.TYPE_DATE.equals( methodParamDataType ) ) {
try {
new SimpleDateFormat( DateUtils.getDateFormatMask(), Locale.ENGLISH ).parse( adjustParam( methodParamDataType,
paramValue,
Collections.EMPTY_MAP,
isJavaDialect ) );
return methodParamDataType;
} catch ( ParseException e ) {
return null;
}
} else if ( DataType.TYPE_STRING.equals( methodParamDataType ) ) {
if ( paramValue.startsWith( "\"" ) ) {
return methodParamDataType;
}
} else if ( DataType.TYPE_NUMERIC.equals( methodParamDataType ) ) {
if ( !NumberUtils.isNumber( paramValue ) ) {
return methodParamDataType;
}
} else if ( DataType.TYPE_NUMERIC_BIGDECIMAL.equals( methodParamDataType ) ) {
try {
new BigDecimal( adjustParam( methodParamDataType,
paramValue,
Collections.EMPTY_MAP,
isJavaDialect ) );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_BIGINTEGER.equals( methodParamDataType ) ) {
try {
new BigInteger( adjustParam( methodParamDataType,
paramValue,
Collections.EMPTY_MAP,
isJavaDialect ) );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_BYTE.equals( methodParamDataType ) ) {
try {
new Byte( paramValue );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_DOUBLE.equals( methodParamDataType ) ) {
try {
new Double( paramValue );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_FLOAT.equals( methodParamDataType ) ) {
try {
new Float( paramValue );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_INTEGER.equals( methodParamDataType ) ) {
try {
new Integer( paramValue );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_LONG.equals( methodParamDataType ) ) {
try {
new Long( paramValue );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
} else if ( DataType.TYPE_NUMERIC_SHORT.equals( methodParamDataType ) ) {
try {
new Short( paramValue );
return methodParamDataType;
} catch ( NumberFormatException e ) {
return null;
}
}
return null;
}
}
}