/*
* Copyright 2010 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.eclipse.editors.completion;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import org.drools.compiler.builder.impl.KnowledgeBuilderConfigurationImpl;
import org.drools.compiler.compiler.PackageRegistry;
import org.drools.compiler.lang.Location;
import org.drools.compiler.lang.descr.GlobalDescr;
import org.drools.compiler.rule.builder.dialect.mvel.MVELConsequenceBuilder;
import org.drools.compiler.rule.builder.dialect.mvel.MVELDialect;
import org.drools.core.base.ClassTypeResolver;
import org.drools.core.rule.MVELDialectRuntimeData;
import org.drools.core.spi.KnowledgeHelper;
import org.drools.core.util.asm.ClassFieldInspector;
import org.drools.eclipse.DRLInfo;
import org.drools.eclipse.DRLInfo.RuleInfo;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.DroolsPluginImages;
import org.drools.eclipse.editors.AbstractRuleEditor;
import org.drools.eclipse.util.ProjectClassLoader;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IFileEditorInput;
import org.kie.api.runtime.rule.RuleContext;
import org.mvel2.MVEL;
import org.mvel2.ParserConfiguration;
import org.mvel2.ParserContext;
import org.mvel2.compiler.CompiledExpression;
import org.mvel2.compiler.ExecutableStatement;
import org.mvel2.compiler.PropertyVerifier;
/**
* For handling within rules.
*/
public class RuleCompletionProcessor extends DefaultCompletionProcessor {
private static final String DIALECT = "dialect";
private static final Image DROOLS_ICON = DroolsPluginImages.getImage( DroolsPluginImages.DROOLS );
private static final Image CLASS_ICON = DroolsPluginImages.getImage( DroolsPluginImages.CLASS );
/**
* A CompletionContext contains the DRL backtext parsing results, to avoid
* multilpe parser invocations
*/
private CompletionContext context;
public RuleCompletionProcessor(AbstractRuleEditor editor) {
super( editor );
}
protected List<ICompletionProposal> getCompletionProposals(ITextViewer viewer, int documentOffset) {
try {
final List<ICompletionProposal> list = new ArrayList<ICompletionProposal>();
IDocument doc = viewer.getDocument();
String backText = readBackwards( documentOffset,
doc );
final String prefix = CompletionUtil.stripLastWord( backText );
// if inside the keyword "rule ", no code completion
if ( backText.length() < 5 ) {
return list;
}
this.context = new CompletionContext( backText );
Location location = context.getLocation();
if ( location.getType() == Location.LOCATION_RULE_HEADER ) {
addRuleHeaderProposals( list,
documentOffset,
prefix,
backText );
} else if ( location.getType() == Location.LOCATION_RHS ) {
addRHSCompletionProposals( list,
documentOffset,
prefix,
backText,
(String) location.getProperty( Location.LOCATION_LHS_CONTENT ),
(String) location.getProperty( Location.LOCATION_RHS_CONTENT ) );
} else {
addLHSCompletionProposals( list,
documentOffset,
location,
prefix,
backText );
}
filterProposalsOnPrefix(prefix, list);
return list;
} catch ( Throwable t ) {
DroolsEclipsePlugin.log( t );
}
return null;
}
protected void addRHSCompletionProposals(List<ICompletionProposal> list,
int documentOffset,
String prefix,
String backText,
String conditions,
String consequence) {
// only add functions and keywords if at the beginning of a
// new statement
if ( consequence == null || consequence.length() < prefix.length() ) {
// possible if doing code completion directly after "then"
return;
}
String consequenceWithoutPrefix = consequence.substring( 0,
consequence.length() - prefix.length() );
if ( context == null ) {
context = new CompletionContext( backText );
}
boolean startOfDialectExpression = CompletionUtil.isStartOfDialectExpression( consequenceWithoutPrefix );
if ( //isJavaDialect() &&
startOfDialectExpression ) {
addRHSKeywordCompletionProposals( list,
documentOffset,
prefix );
addRHSFunctionCompletionProposals( list,
documentOffset,
prefix );
}
//if we have 1st a dialect defined locally, or 2nd a global dialect
//the locally defined dialect will override the package default
if ( isJavaDialect() ) {
addRHSJavaCompletionProposals( list,
documentOffset,
prefix,
backText,
consequence );
} else if ( isMvelDialect() ) {
addRHSMvelCompletionProposals( list,
documentOffset,
prefix,
backText,
consequence,
startOfDialectExpression );
}
}
private boolean isJavaDialect() {
// java is the default dialect, so no package dialect means java
// conditions are ordered from the more specific to the more general
if ( context.isJavaDialect() ) {
return true;
} else if ( context.isDefaultDialect() && (!(getAttributes().containsKey( DIALECT )) || hasPackageDialect( "java" )) ) {
return true;
}
return false;
}
private boolean isMvelDialect() {
if ( context.isMvelDialect() ) {
return true;
} else if ( context.isDefaultDialect() && hasPackageDialect( "mvel" ) ) {
return true;
}
return false;
}
private boolean hasPackageDialect(String dialect) {
String globalDialect = (String) getAttributes().get( DIALECT );
if ( globalDialect != null && dialect.equalsIgnoreCase( globalDialect ) ) {
return true;
}
return false;
}
protected void addLHSCompletionProposals(List<ICompletionProposal> list,
int documentOffset,
Location location,
String prefix,
String backText) {
switch ( location.getType() ) {
case Location.LOCATION_LHS_BEGIN_OF_CONDITION :
// if we are at the beginning of a new condition
// add drools keywords
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"and",
"and ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"or",
"or ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"from",
"from ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"forall",
"forall( )",
8,
DROOLS_ICON ) );
RuleCompletionProposal prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"eval",
"eval( )",
6 );
prop.setImage( DROOLS_ICON );
list.add( prop );
prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"then",
"then" + System.getProperty( "line.separator" ) + "\t" );
prop.setImage( DROOLS_ICON );
list.add( prop );
// we do not break but also add all elements that are needed for
// and/or
case Location.LOCATION_LHS_BEGIN_OF_CONDITION_AND_OR :
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"not",
"not ",
DROOLS_ICON ) );
// we do not break but also add all elements that are needed for
// not
case Location.LOCATION_LHS_BEGIN_OF_CONDITION_NOT :
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"exists",
"exists ",
DROOLS_ICON ) );
// we do not break but also add all elements that are needed for
// exists
case Location.LOCATION_LHS_FROM_ACCUMULATE :
case Location.LOCATION_LHS_FROM_COLLECT :
case Location.LOCATION_LHS_BEGIN_OF_CONDITION_EXISTS :
// and add imported classes
Iterator<String> iterator = getImports().iterator();
while ( iterator.hasNext() ) {
String name = iterator.next();
int index = name.lastIndexOf( "." );
if ( index != -1 ) {
String className = name.substring( index + 1 );
RuleCompletionProposal p = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
className,
className + "( )",
className.length() + 2 );
p.setPriority( -1 );
p.setImage( CLASS_ICON );
list.add( p );
}
}
iterator = getClassesInPackage().iterator();
while ( iterator.hasNext() ) {
String name = iterator.next();
int index = name.lastIndexOf( "." );
if ( index != -1 ) {
String className = name.substring( index + 1 );
RuleCompletionProposal p = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
className,
className + "( )",
className.length() + 2 );
p.setPriority( -1 );
p.setImage( CLASS_ICON );
list.add( p );
}
}
break;
case Location.LOCATION_LHS_INSIDE_CONDITION_START :
String className = (String) location.getProperty( Location.LOCATION_PROPERTY_CLASS_NAME );
String propertyName = (String) location.getProperty( Location.LOCATION_PROPERTY_PROPERTY_NAME );
if ( className != null ) {
ClassTypeResolver resolver = new ClassTypeResolver( getUniqueImports(),
ProjectClassLoader.getProjectClassLoader( getEditor() ) );
try {
String currentClass = className;
if ( propertyName != null ) {
String[] nestedProperties = propertyName.split( "\\." );
int nbSuperProperties = nestedProperties.length - 1;
if ( propertyName.endsWith( "." ) ) {
nbSuperProperties++;
}
for ( int i = 0; i < nbSuperProperties; i++ ) {
String simplePropertyName = nestedProperties[i];
currentClass = getSimplePropertyClass( currentClass,
simplePropertyName );
currentClass = convertToNonPrimitiveClass( currentClass );
}
}
RuleCompletionProposal p = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"this" );
p.setImage( METHOD_ICON );
list.add( p );
Class<?> clazz = resolver.resolveType( currentClass );
if ( clazz != null ) {
if ( Map.class.isAssignableFrom( clazz ) ) {
p = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"this['']",
"this['']",
6 );
p.setImage( METHOD_ICON );
list.add( p );
}
ClassFieldInspector inspector = new ClassFieldInspector( clazz );
Map<String, Class<?>> types = inspector.getFieldTypes();
Iterator<String> iterator2 = inspector.getFieldNames().keySet().iterator();
while ( iterator2.hasNext() ) {
String name = iterator2.next();
p = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
name,
name + " " );
p.setImage( METHOD_ICON );
list.add( p );
Class<?> type = types.get( name );
if ( type != null && Map.class.isAssignableFrom( type ) ) {
name += "['']";
p = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
name,
name,
name.length() - 2 );
p.setImage( METHOD_ICON );
list.add( p );
}
}
}
} catch ( IOException exc ) {
// Do nothing
} catch ( ClassNotFoundException exc ) {
// Do nothing
}
}
break;
case Location.LOCATION_LHS_INSIDE_CONDITION_OPERATOR :
className = (String) location.getProperty( Location.LOCATION_PROPERTY_CLASS_NAME );
String property = (String) location.getProperty( Location.LOCATION_PROPERTY_PROPERTY_NAME );
String type = getPropertyClass( className,
property );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"==",
"== ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"!=",
"!= ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
":",
": ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"->",
"-> ( )",
5,
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"memberOf",
"memberOf ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"not memberOf",
"not memberOf ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"in",
"in ( )",
5,
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"not in",
"not in ( )",
9,
DROOLS_ICON ) );
if ( isComparable( type ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"<",
"< ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"<=",
"<= ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
">",
"> ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
">=",
">= ",
DROOLS_ICON ) );
}
if ( type.equals( "java.lang.String" ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"matches",
"matches \"\"",
9,
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"not matches",
"not matches \"\"",
13,
DROOLS_ICON ) );
}
if ( isSubtypeOf( type,
"java.util.Collection" ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"contains",
"contains ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"not contains",
"not contains ",
DROOLS_ICON ) );
}
break;
case Location.LOCATION_LHS_INSIDE_CONDITION_ARGUMENT :
// determine type
className = (String) location.getProperty( Location.LOCATION_PROPERTY_CLASS_NAME );
property = (String) location.getProperty( Location.LOCATION_PROPERTY_PROPERTY_NAME );
String operator = (String) location.getProperty( Location.LOCATION_PROPERTY_OPERATOR );
type = getPropertyClass( className,
property );
if ( "in".equals( operator ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"()",
"( )",
2,
DROOLS_ICON ) );
break;
}
if ( "contains".equals( operator ) || "excludes".equals( operator ) ) {
type = "java.lang.Object";
}
if ( "memberOf".equals( operator ) ) {
type = "java.util.Collection";
}
boolean isObject = false;
if ( "java.lang.Object".equals( type ) ) {
isObject = true;
}
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"null",
"null ",
DROOLS_ICON ) );
if ( "boolean".equals( type ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"true",
"true ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"false",
"false ",
DROOLS_ICON ) );
}
if ( isObject || "java.lang.String".equals( type ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"\"\"",
"\"\"",
1,
DROOLS_ICON ) );
}
if ( isObject || "java.util.Date".equals( type ) ) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"\"dd-mmm-yyyy\"",
"\"dd-mmm-yyyy\"",
1,
DROOLS_ICON ) );
}
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"()",
"( )",
2,
DROOLS_ICON ) );
// add parameters with possibly matching type
Map<String, String> result = new HashMap<String, String>();
addRuleParameters( result, context.getRuleParameters() );
Iterator<Entry<String, String>> iterator2 = result.entrySet().iterator();
while ( iterator2.hasNext() ) {
Entry<String, String> entry = iterator2.next();
String paramName = entry.getKey();
String paramType = entry.getValue();
if ( isSubtypeOf( paramType,
type ) ) {
RuleCompletionProposal proposal = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
paramName );
proposal.setPriority( -1 );
proposal.setImage( VARIABLE_ICON );
list.add( proposal );
}
}
// add globals with possibly matching type
List<GlobalDescr> globals = getGlobals();
if ( globals != null ) {
for ( GlobalDescr global: globals ) {
if ( isSubtypeOf( global.getType(),
type ) ) {
RuleCompletionProposal proposal = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
global.getIdentifier() );
proposal.setPriority( -1 );
proposal.setImage( VARIABLE_ICON );
list.add( proposal );
}
}
}
break;
case Location.LOCATION_LHS_INSIDE_EVAL :
String content = (String) location.getProperty( Location.LOCATION_EVAL_CONTENT );
list.addAll( getJavaCompletionProposals( documentOffset,
content,
prefix,
getRuleParameters( backText ) ) );
break;
case Location.LOCATION_LHS_INSIDE_CONDITION_END :
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"&&",
"&& ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"||",
"|| ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
",",
", ",
DROOLS_ICON ) );
break;
case Location.LOCATION_LHS_FROM :
String fromText = (String) location.getProperty( Location.LOCATION_FROM_CONTENT );
int index = fromText.indexOf( '.' );
if ( index == -1 ) {
// add accumulate and collect keyword
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"accumulate",
"accumulate ( , init ( ), action ( ), result ( ) )",
13,
DROOLS_ICON ) );
KnowledgeBuilderConfigurationImpl config = new KnowledgeBuilderConfigurationImpl( ProjectClassLoader.getProjectClassLoader( getEditor() ),
null );
for (String accumulateFunction : config.getAccumulateFunctionNames()) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"accumulate " + accumulateFunction,
"accumulate ( , " + accumulateFunction + "( ) )",
13,
DROOLS_ICON ) );
}
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"collect",
"collect ( )",
10,
DROOLS_ICON ) );
// add all functions
if ( "".equals( fromText ) ) {
List<String> functions = getFunctions();
iterator = functions.iterator();
while ( iterator.hasNext() ) {
String name = iterator.next() + "()";
prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
name,
name,
name.length() - 1 );
prop.setPriority( -1 );
prop.setImage( METHOD_ICON );
list.add( prop );
}
}
list.addAll( getJavaCompletionProposals( documentOffset,
fromText,
prefix,
getRuleParameters( backText ) ) );
}
break;
case Location.LOCATION_LHS_FROM_ACCUMULATE_INIT_INSIDE :
content = (String) location.getProperty( Location.LOCATION_PROPERTY_FROM_ACCUMULATE_INIT_CONTENT );
list.addAll( getJavaCompletionProposals( documentOffset,
content,
prefix,
getRuleParameters( backText ) ) );
break;
case Location.LOCATION_LHS_FROM_ACCUMULATE_ACTION_INSIDE :
content = (String) location.getProperty( Location.LOCATION_PROPERTY_FROM_ACCUMULATE_INIT_CONTENT );
content += (String) location.getProperty( Location.LOCATION_PROPERTY_FROM_ACCUMULATE_ACTION_CONTENT );
list.addAll( getJavaCompletionProposals( documentOffset,
content,
prefix,
getRuleParameters( backText ) ) );
break;
case Location.LOCATION_LHS_FROM_ACCUMULATE_RESULT_INSIDE :
content = (String) location.getProperty( Location.LOCATION_PROPERTY_FROM_ACCUMULATE_INIT_CONTENT );
content += (String) location.getProperty( Location.LOCATION_PROPERTY_FROM_ACCUMULATE_ACTION_CONTENT );
content += (String) location.getProperty( Location.LOCATION_PROPERTY_FROM_ACCUMULATE_RESULT_CONTENT );
list.addAll( getJavaCompletionProposals( documentOffset,
content,
prefix,
getRuleParameters( backText ) ) );
break;
}
}
private String getPropertyClass(String className,
String propertyName) {
if ( className != null && propertyName != null ) {
String[] nestedProperties = propertyName.split( "\\." );
String currentClass = className;
for ( int i = 0; i < nestedProperties.length; i++ ) {
String simplePropertyName = nestedProperties[i];
currentClass = getSimplePropertyClass( currentClass,
simplePropertyName );
}
return currentClass;
}
return null;
}
private String getSimplePropertyClass(String className,
String propertyName) {
if ( "this".equals( propertyName ) ) {
return className;
}
if ( propertyName.endsWith( "]" ) ) {
// TODO can we take advantage of generics here?
return "java.lang.Object";
}
ClassTypeResolver resolver = new ClassTypeResolver( getUniqueImports(),
ProjectClassLoader.getProjectClassLoader( getEditor() ) );
try {
Class<?> clazz = resolver.resolveType( className );
if ( clazz != null ) {
Class<?> clazzz = new ClassFieldInspector( clazz ).getFieldTypes().get( propertyName );
if ( clazzz != null ) {
return clazzz.getName();
}
}
} catch ( IOException exc ) {
// Do nothing
} catch ( ClassNotFoundException exc ) {
// Do nothing
}
return "java.lang.Object";
}
private Map<String, String> getRuleParameters(String backText) {
Map<String, String> result = new HashMap<String, String>();
// add globals
List<GlobalDescr> globals = getGlobals();
if ( globals != null ) {
for (GlobalDescr global : globals) {
result.put( global.getIdentifier(),
global.getType() );
}
}
if ( context == null ) {
context = new CompletionContext( backText );
}
addRuleParameters( result, context.getRuleParameters() );
return result;
}
private boolean isComparable(String type) {
if ( type == null ) {
return false;
}
if ( isPrimitiveNumericType( type ) ) {
return true;
}
if ( isObjectNumericType( type ) ) {
return true;
}
if ( isSubtypeOf( type,
"java.lang.Comparable" ) ) {
return true;
}
return false;
}
private boolean isPrimitiveType(String type) {
return isPrimitiveNumericType( type ) || type.equals( "boolean" );
}
private boolean isPrimitiveNumericType(String type) {
return type.equals( "byte" ) || type.equals( "short" ) || type.equals( "int" ) || type.equals( "long" ) || type.equals( "float" ) || type.equals( "double" ) || type.equals( "char" );
}
private boolean isObjectNumericType(String type) {
return type.equals( "java.lang.Byte" ) || type.equals( "java.lang.Short" ) || type.equals( "java.lang.Integer" ) || type.equals( "java.lang.Long" ) || type.equals( "java.lang.Float" ) || type.equals( "java.lang.Double" )
|| type.equals( "java.lang.Char" );
}
/**
* Returns true if the first class is the same or a subtype of the second
* class.
*
* @param class1
* @param class2
* @return
*/
private boolean isSubtypeOf(String class1,
String class2) {
if ( class1 == null || class2 == null ) {
return false;
}
class1 = convertToNonPrimitiveClass( class1 );
class2 = convertToNonPrimitiveClass( class2 );
// TODO add code to take primitive types into account
ClassTypeResolver resolver = new ClassTypeResolver( getUniqueImports(),
ProjectClassLoader.getProjectClassLoader( getEditor() ) );
try {
Class<?> clazz1 = resolver.resolveType( class1 );
Class<?> clazz2 = resolver.resolveType( class2 );
if ( clazz1 == null || clazz2 == null ) {
return false;
}
return clazz2.isAssignableFrom( clazz1 );
} catch ( ClassNotFoundException exc ) {
return false;
}
}
private String convertToNonPrimitiveClass(String clazz) {
if ( !isPrimitiveType( clazz ) ) {
return clazz;
}
if ( "byte".equals( clazz ) ) {
return "java.lang.Byte";
} else if ( "short".equals( clazz ) ) {
return "java.lang.Short";
} else if ( "int".equals( clazz ) ) {
return "java.lang.Integer";
} else if ( "long".equals( clazz ) ) {
return "java.lang.Long";
} else if ( "float".equals( clazz ) ) {
return "java.lang.Float";
} else if ( "double".equals( clazz ) ) {
return "java.lang.Double";
} else if ( "char".equals( clazz ) ) {
return "java.lang.Char";
} else if ( "boolean".equals( clazz ) ) {
return "java.lang.Boolean";
}
// should never occur
return null;
}
private void addRHSFunctionCompletionProposals(List<ICompletionProposal> list, int documentOffset, String prefix) {
Iterator<String> iterator;
RuleCompletionProposal prop;
List<String> functions = getFunctions();
iterator = functions.iterator();
while ( iterator.hasNext() ) {
String name = iterator.next() + "()";
prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
name,
name + ";",
name.length() - 1 );
prop.setPriority( -1 );
prop.setImage( METHOD_ICON );
list.add( prop );
}
}
private void addRHSKeywordCompletionProposals(List<ICompletionProposal> list, int documentOffset, String prefix) {
RuleCompletionProposal prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"update",
"update();",
7 );
prop.setImage( DROOLS_ICON );
list.add( prop );
prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"retract",
"retract();",
8 );
prop.setImage( DROOLS_ICON );
list.add( prop );
prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"insert",
"insert();",
7 );
prop.setImage( DROOLS_ICON );
list.add( prop );
prop = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"insertLogical",
"insertLogical();",
14 );
prop.setImage( DROOLS_ICON );
list.add( prop );
}
private void addRHSJavaCompletionProposals(List<ICompletionProposal> list,
int documentOffset,
String prefix,
String backText,
String consequence) {
list.addAll( getJavaCompletionProposals( documentOffset,
consequence,
prefix,
getRuleParameters( backText ) ) );
}
private void addRHSMvelCompletionProposals(List<ICompletionProposal> list,
final int documentOffset,
String prefix,
String backText,
String consequence,
boolean expressionStart) {
Collection<ICompletionProposal> mvelCompletionProposals = getMvelCompletionProposals( consequence,
documentOffset,
prefix,
getRuleParameters( backText ),
backText,
expressionStart );
list.addAll( mvelCompletionProposals );
}
private Collection<ICompletionProposal> getMvelCompletionProposals(final String consequenceBackText,
final int documentOffset,
final String prefix,
Map<String, String> params,
String ruleBackText,
boolean startOfExpression) {
final Set<ICompletionProposal> proposals = new HashSet<ICompletionProposal>();
if (!(getEditor().getEditorInput() instanceof IFileEditorInput)) {
return proposals;
}
try {
DRLInfo drlInfo = DroolsEclipsePlugin.getDefault().generateParsedResource(
"package dummy; \n" + ruleBackText,
((IFileEditorInput) getEditor().getEditorInput()).getFile(),
false,
false );
String textWithoutPrefix = CompletionUtil.getTextWithoutPrefix( consequenceBackText,
prefix );
boolean expressionStart = CompletionUtil.isStartOfDialectExpression( textWithoutPrefix );
boolean isConstrained = textWithoutPrefix.endsWith( "." );
// we split the expression in various regions:
// *the previous expression
// *the last expression
// *the last inner expression
// attempt to compile and analyze the previous expression to collect inputs and vars
String previousExpression = CompletionUtil.getPreviousExpression( consequenceBackText );
MvelContext previousExprContext = analyzeMvelExpression( getResolvedMvelInputs( params ),
drlInfo,
previousExpression );
// attempt to compile and analyze the last and last inner expression, using as inputs the previous expression inputs and vars
@SuppressWarnings("rawtypes")
Map<String, Class> variables = previousExprContext.getContext().getVariables();
@SuppressWarnings("rawtypes")
Map<String, Class> inputs = previousExprContext.getContext().getInputs();
inputs.putAll( variables );
//last inner expression
String lastInnerExpression = CompletionUtil.getTextWithoutPrefix( CompletionUtil.getInnerExpression( consequenceBackText ),
prefix );
String compilableLastInnerExpression = CompletionUtil.getCompilableText( lastInnerExpression );
MvelContext lastInnerExprContext = analyzeMvelExpression( inputs,
drlInfo,
compilableLastInnerExpression );
//last expression
String lastExpression = CompletionUtil.getLastExpression( consequenceBackText ).trim();
//is this a modify expression?
//group 1 is the body of modify
//group 2 if present is the whole with block including brackets
//group 3 if present is the inner content of the with block
Matcher modMatcher = CompletionUtil.MODIFY_PATTERN.matcher( lastExpression );
boolean isModifyBlock = modMatcher.matches() && modMatcher.groupCount() == 3;
//if constrained, get completion for egress of last inner, filtered on prefix
if ( isConstrained ) {
if ( lastInnerExprContext.isStaticFlag() ) {
return getMvelClassCompletionsFromJDT( documentOffset,
"",
params,
lastInnerExprContext.getReturnedType() );
}
return getMvelInstanceCompletionsFromJDT( documentOffset,
"",
params,
lastInnerExprContext.getReturnedType(),
false );
}
//if expression start inside with block, then get completion for prefix with egrss of modif var + prev expr var&inputs
else if ( expressionStart && isModifyBlock ) {
String modifyVar = modMatcher.group( 1 );
//String modifyWith = modMatcher.group( 3 );
//get the egress type of the modify var
MvelContext modVarContext = analyzeMvelExpression( inputs,
drlInfo,
modifyVar );
Class<?> modVarType = modVarContext.getReturnedType();
Collection<ICompletionProposal> modVarComps = getMvelInstanceCompletionsFromJDT( documentOffset,
"",
params,
modVarType,
true );
proposals.addAll( modVarComps );
// addMvelCompletions( proposals,
// documentOffset,
// "",
// lastInnerExprContext.getContext().getVariables() );
//
// addMvelCompletions( proposals,
// documentOffset,
// "",
// lastInnerExprContext.getContext().getInputs() );
//
// Collection jdtProps = getJavaCompletionProposals( documentOffset,
// prefix,
// prefix,
// params );
//
// proposals.addAll( jdtProps );
return proposals;
}
//If expression start, and all other cases then get completion for prefix with prev expr var&inputs
addMvelCompletions( proposals,
documentOffset,
prefix,
lastInnerExprContext.getContext().getVariables() );
addMvelCompletions( proposals,
documentOffset,
prefix,
lastInnerExprContext.getContext().getInputs() );
Collection<ICompletionProposal> jdtProps = getJavaCompletionProposals( documentOffset,
prefix,
prefix,
params );
proposals.addAll( jdtProps );
} catch ( Throwable e ) {
DroolsEclipsePlugin.log( e );
}
Set<ICompletionProposal> uniqueProposals = new HashSet<ICompletionProposal>();
addAllNewProposals( uniqueProposals,
proposals );
return uniqueProposals;
}
@SuppressWarnings("rawtypes")
private Map<String, Class> getResolvedMvelInputs(Map<String, String> params) {
ClassTypeResolver resolver = new ClassTypeResolver(getUniqueImports(),
ProjectClassLoader.getProjectClassLoader(getEditor()));
Map<String, Class> resolved = new HashMap<String, Class>();
for (Entry<String, String> entry : params.entrySet()) {
String inputType = entry.getValue();
try {
Class<?> type = resolver.resolveType( inputType );
resolved.put( entry.getKey(), type );
} catch ( ClassNotFoundException e ) {
DroolsEclipsePlugin.log( e );
}
}
return resolved;
}
class MvelContext {
private CompiledExpression expression;
private ParserContext parserContext;
private Class<?> returnedType;
private boolean staticFlag;
public ParserContext getContext() {
return parserContext;
}
void setContext(ParserContext parserContext) {
this.parserContext = parserContext;
}
void setExpression(CompiledExpression expression) {
this.expression = expression;
}
CompiledExpression getExpression() {
return expression;
}
void setReturnedType(Class<?> returnedType) {
this.returnedType = returnedType;
}
Class<?> getReturnedType() {
return returnedType;
}
public boolean isStaticFlag() {
return staticFlag;
}
public void setStaticFlag(boolean staticFlag) {
this.staticFlag = staticFlag;
}
}
private MvelContext analyzeMvelExpression(@SuppressWarnings("rawtypes") Map<String, Class> params,
DRLInfo drlInfo,
String mvel) {
String expr = processMacros( mvel );
String name = context.getRuleName();
RuleInfo currentRule = getCurrentRule( drlInfo,
name );
String qName = drlInfo.getPackageName() + "." + name;
MVELDialect dialect = (MVELDialect) drlInfo.getDialectRegistry().getDialect("mvel");
ParserContext initialContext = createInitialContext( params,
qName,
dialect );
MvelContext mCon = new MvelContext();
mCon.setContext( initialContext );
try {
ExecutableStatement stmt = (ExecutableStatement) MVEL.compileExpression(expr, initialContext);
Class<?> lastType = stmt.getKnownEgressType();
//Statics expression may return Class as an egress type
if ( lastType != null && "java.lang.Class".equals( lastType.getName() ) ) {
mCon.setStaticFlag( true );
}
if ( lastType == null || "java.lang.Object".equals( lastType.getName() ) || "java.lang.Class".equals( lastType.getName() ) ) {
// attempt to use the property verifier to get
// a better type resolution (a recommend by cbrock, though egress gives consistent results)
lastType = new PropertyVerifier( expr, initialContext ).analyze();
}
if ( lastType == null ) {
lastType = Object.class;
}
mCon.setReturnedType( lastType );
} catch ( Exception e ) {
//do nothing while doing completion.
}
return mCon;
}
@SuppressWarnings("unchecked")
private static ParserContext createInitialContext(@SuppressWarnings("rawtypes") Map<String, Class> params,
String qualifiedName,
MVELDialect dialect) {
//Lookup ParserConfiguration to retrieve imports and package imports
PackageRegistry packageRegistry = dialect.getPackageRegistry();
MVELDialectRuntimeData data = (MVELDialectRuntimeData) packageRegistry.getDialectRuntimeRegistry().getDialectData( dialect.getId() );
ParserConfiguration pconf = data.getParserConfiguration();
final ParserContext context = new ParserContext( pconf.getImports(),
null,
qualifiedName );
if ( pconf.getPackageImports() != null ) {
for (String packageImport : pconf.getPackageImports()) {
context.addPackageImport( packageImport );
}
}
context.setStrictTypeEnforcement( false );
context.setInterceptors( dialect.getInterceptors() );
context.setInputs( params );
context.addInput("drools", KnowledgeHelper.class);
context.addInput("kcontext", RuleContext.class);
context.setCompiled( true );
return context;
}
public static String processMacros(String mvel) {
String macrosProcessedCompilableConsequence = MVELConsequenceBuilder.processMacros( mvel.trim() );
return macrosProcessedCompilableConsequence;
}
private static RuleInfo getCurrentRule(DRLInfo drlInfo,
String currentRulename) {
RuleInfo currentRule = null;
RuleInfo[] ruleInfos = drlInfo.getRuleInfos();
for ( int i = 0; i < ruleInfos.length; i++ ) {
if ( currentRulename.equals( ruleInfos[i].getRuleName() ) ) {
currentRule = ruleInfos[i];
break;
}
}
return currentRule;
}
/*
* Completions for object instance members
*/
private Collection<ICompletionProposal> getMvelInstanceCompletionsFromJDT(final int documentOffset,
final String prefix,
Map<String, String> params,
Class<?> lastType,
boolean settersOnly) {
if ( lastType == null ) {
lastType = Object.class;
}
//FIXME: there is a small chance of var name collision using this arbitrary mvdrlofc as a variable name.
//ideally the variable name should be inferred from the last member of the expression
final String syntheticVarName = "mvdrlofc";
String javaText = "\n" + lastType.getPackage().getName() + "." + CompletionUtil.getSimpleClassName( lastType ) + " " + syntheticVarName + ";\n" + syntheticVarName + ".";
final List<ICompletionProposal> list1 = new ArrayList<ICompletionProposal>();
requestJavaCompletionProposals(javaText, prefix, documentOffset, params, list1);
final List<ICompletionProposal> list = list1;
Collection<ICompletionProposal> mvelList = RuleCompletionProcessor.mvelifyProposals(list, settersOnly);
return mvelList;
}
/*
* Completions for static Class members
*/
private Collection<ICompletionProposal> getMvelClassCompletionsFromJDT(final int documentOffset,
final String prefix,
Map<String, String> params,
Class<?> lastType) {
if ( lastType == null ) {
lastType = Object.class;
}
//FIXME: there is a small chance of var name collision using this arbitrary mvdrlofc as a variable name.
//ideally the variable name should be inferred from the last member of the expression
String javaText = "\n" + CompletionUtil.getSimpleClassName( lastType ) + ".";
final List<ICompletionProposal> list1 = new ArrayList<ICompletionProposal>();
requestJavaCompletionProposals(javaText, prefix, documentOffset, params, list1);
final List<ICompletionProposal> list = list1;
Collection<ICompletionProposal> mvelList = RuleCompletionProcessor.mvelifyProposals(list, false);
return mvelList;
}
@SuppressWarnings("rawtypes")
private static void addMvelCompletions(final Collection<ICompletionProposal> proposals,
int documentOffset,
String prefix,
Map<String, Class> inputs) {
Set<ICompletionProposal> newProposals = new HashSet<ICompletionProposal>();
for (Entry<String, Class> entry : inputs.entrySet()) {
String prop = entry.getKey();
Class<?> type = entry.getValue();
String display = prop + " " + CompletionUtil.getSimpleClassName( type );
RuleCompletionProposal rcp = new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
display,
prop );
rcp.setImage( DefaultCompletionProcessor.VARIABLE_ICON );
newProposals.add( rcp );
}
addAllNewProposals(proposals, newProposals);
}
public static void addAllNewProposals(final Collection<ICompletionProposal> proposals,
final Collection<ICompletionProposal> newProposals) {
for (ICompletionProposal newProp : newProposals) {
String displayString = newProp.getDisplayString();
//JBRULES-1134 do not add completions if they already exist
if (!containsProposal(proposals, displayString)) {
proposals.add(newProp);
}
}
}
/**
* Attempt to compare proposals of different types based on the tokenized display string
* @param proposals
* @param newProposal
* @return true if the collection contains a proposal which matches the new Proposal.
* The match is based on the first token based on a space split
*/
public static boolean containsProposal(final Collection<ICompletionProposal> proposals, String newProposal) {
for (ICompletionProposal prop : proposals) {
String displayString = prop.getDisplayString();
String[] existings = displayString.split( " " );
if ( existings.length == 0 ) {
continue;
}
String[] newProposals = newProposal.split( " " );
if ( newProposals.length == 0 ) {
continue;
}
if ( existings[0].equals( newProposals[0] ) ) {
return true;
}
}
return false;
}
private void addRuleParameters(Map<String, String> result,
Map<String, String[]> ruleParameters) {
for (Map.Entry<String, String[]> entry: ruleParameters.entrySet()) {
String name = entry.getKey();
String clazz = entry.getValue()[0];
String field = entry.getValue()[1];
String type;
if (field == null) {
type = clazz;
} else {
type = getPropertyClass( clazz, field );
}
result.put( name, type );
}
}
private void addRuleHeaderProposals(List<ICompletionProposal> list,
int documentOffset,
String prefix,
String backText) {
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"salience",
"salience ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"no-loop",
"no-loop ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"agenda-group",
"agenda-group ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"duration",
"duration ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"auto-focus",
"auto-focus ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"when",
"when" + System.getProperty( "line.separator" ) + "\t ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"activation-group",
"activation-group ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"date-effective",
"date-effective \"dd-MMM-yyyy\"",
16,
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"date-expires",
"date-expires \"dd-MMM-yyyy\"",
14,
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"enabled",
"enabled false",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"ruleflow-group",
"ruleflow-group \"\"",
16,
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"lock-on-active",
"lock-on-active ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"dialect \"java\"",
"dialect \"java\" ",
DROOLS_ICON ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"dialect \"mvel\"",
"dialect \"mvel\" ",
DROOLS_ICON ) );
}
/*
* Filters accessor method proposals to replace them with their mvel expression equivalent
* For instance a completion for getStatus() would be replaced by a completion for status
* when asking for stters only, then only setters or writable fields will be returned
*/
public static Collection<ICompletionProposal> mvelifyProposals(List<ICompletionProposal> list, boolean settersOnly) {
final Collection<ICompletionProposal> set = new HashSet<ICompletionProposal>();
for (ICompletionProposal o : list) {
if ( o instanceof JavaMethodCompletionProposal ) {
//methods
processJavaMethodCompletionProposal(list, settersOnly, set, o);
} else if ( o instanceof JavaCompletionProposal ) {
//fields
processesJavaCompletionProposal(settersOnly, set, o);
} else if ( !settersOnly ) {
set.add( o );
}
}
return set;
}
private static void processesJavaCompletionProposal(boolean settersOnly,
final Collection<ICompletionProposal> set,
ICompletionProposal o) {
if ( settersOnly ) {
JavaCompletionProposal jcp = (JavaCompletionProposal) o;
//TODO: FIXME: this is very fragile as it uses reflection to access the private completion field.
//Yet this is needed to do mvel filtering based on the method signtures, IF we use the richer JDT completion
// Object field = ReflectionUtils.getField( o,
// "fProposal" );
IJavaElement javaElement = jcp.getJavaElement();
if ( javaElement.getElementType() == IJavaElement.FIELD ) {
set.add( o );
}
} else {
set.add( o );
}
}
private static void processJavaMethodCompletionProposal(List<ICompletionProposal> list,
boolean settersOnly,
final Collection<ICompletionProposal> set,
ICompletionProposal o) {
LazyJavaCompletionProposal javaProposal = (LazyJavaCompletionProposal) o;
//TODO: FIXME: this is very fragile as it uses reflection to access the private completion field.
//Yet this is needed to do mvel filtering based on the method signtures, IF we use the richer JDT completion
Object field = ReflectionUtils.getField(o, "fProposal");
if ( field != null && field instanceof CompletionProposal ) {
CompletionProposal proposal = (CompletionProposal) field;
String completion = new String( proposal.getCompletion() );
String propertyOrMethodName = null;
boolean isSetter = false;
boolean isAccessor = false;
if ( settersOnly ) {
// get the eventual writable property name for that method name and signature
propertyOrMethodName = CompletionUtil.getWritablePropertyName( completion,
proposal.getSignature() );
// if we got a property name that differs from the orginal method name
//then this is a bean accessor
isSetter = !completion.equals( propertyOrMethodName );
} else {
// get the eventual property name for that method name and signature
propertyOrMethodName = CompletionUtil.getPropertyName( completion,
proposal.getSignature() );
//if we got a property name that differs from the orginal method name
//then this is a bean accessor
isAccessor = !completion.equals( propertyOrMethodName );
}
// is the completion for a bean accessor? and do we have already some relevant completion?
boolean doesNotContainFieldCompletion = DefaultCompletionProcessor.doesNotContainFieldCompletion( propertyOrMethodName,
list );
if ( ((settersOnly && isSetter) || (!settersOnly && isAccessor)) && doesNotContainFieldCompletion ) {
//TODO: craft a better JDTish display name than just the property name
RuleCompletionProposal prop = new RuleCompletionProposal( javaProposal.getReplacementOffset(),
javaProposal.getReplacementLength(),
propertyOrMethodName );
prop.setImage( DefaultCompletionProcessor.VARIABLE_ICON );
//set high priority such that the completion for accessors shows up first
prop.setPriority( 1000 );
set.add( prop );
}
else if ( !settersOnly ) {
set.add( o );
}
}
}
}