/*
* Copyright 2010 JBoss Inc
*
* 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.drools.core.util.StringUtils;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.DroolsPluginImages;
import org.drools.eclipse.editors.AbstractRuleEditor;
import org.drools.eclipse.editors.DRLRuleEditor;
import org.drools.lang.descr.FactTemplateDescr;
import org.drools.lang.descr.GlobalDescr;
import org.drools.rule.builder.dialect.java.KnowledgeHelperFixer;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.CompletionContext;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.eval.IEvaluationContext;
import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal;
import org.eclipse.jdt.internal.ui.text.java.LazyJavaTypeCompletionProposal;
import org.eclipse.jdt.ui.text.java.CompletionProposalCollector;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
/**
* This is the basic completion processor that is used when the editor is outside of a rule block
* partition.
* The provides the content assistance for basic rule assembly stuff.
*
* This processor will also read behind the current editing position, to provide some context to
* help provide the pop up list.
*/
public class DefaultCompletionProcessor extends AbstractCompletionProcessor {
private static final String NEW_RULE_TEMPLATE = "rule \"new rule\"" + System.getProperty( "line.separator" ) + "\twhen" + System.getProperty( "line.separator" ) + "\t\t" + System.getProperty( "line.separator" ) + "\tthen"
+ System.getProperty( "line.separator" ) + "\t\t" + System.getProperty( "line.separator" ) + "end";
private static final String NEW_QUERY_TEMPLATE = "query \"query name\"" + System.getProperty( "line.separator" ) + "\t#conditions" + System.getProperty( "line.separator" ) + "end";
private static final String NEW_FUNCTION_TEMPLATE = "function void yourFunction(Type arg) {" + System.getProperty( "line.separator" ) + "\t/* code goes here*/" + System.getProperty( "line.separator" ) + "}";
private static final String NEW_TEMPLATE_TEMPLATE = "template Name" + System.getProperty( "line.separator" ) + "\t" + System.getProperty( "line.separator" ) + "end";
protected static final Pattern IMPORT_PATTERN = Pattern.compile( ".*\n\\W*import\\W[^;\\s]*",
Pattern.DOTALL );
// TODO: doesn't work for { inside functions
private static final Pattern FUNCTION_PATTERN = Pattern.compile( ".*\n\\W*function\\s+(\\S+)\\s+(\\S+)\\s*\\(([^\\)]*)\\)\\s*\\{([^\\}]*)",
Pattern.DOTALL );
protected static final Image VARIABLE_ICON = DroolsPluginImages.getImage( DroolsPluginImages.VARIABLE );
protected static final Image METHOD_ICON = DroolsPluginImages.getImage( DroolsPluginImages.METHOD );
protected static final Image CLASS_ICON = DroolsPluginImages.getImage( DroolsPluginImages.CLASS );
public DefaultCompletionProcessor(AbstractRuleEditor editor) {
super( editor );
}
protected List getCompletionProposals(ITextViewer viewer,
int documentOffset) {
try {
IDocument doc = viewer.getDocument();
String backText = readBackwards( documentOffset,
doc );
String prefix = CompletionUtil.stripLastWord( backText );
List props = null;
Matcher matcher = IMPORT_PATTERN.matcher( backText );
if ( matcher.matches() ) {
String classNameStart = backText.substring( backText.lastIndexOf( "import" ) + 7 );
props = getAllClassProposals( classNameStart,
documentOffset,
prefix );
} else {
matcher = FUNCTION_PATTERN.matcher( backText );
if ( matcher.matches() ) {
// extract function parameters
Map params = extractParams( matcher.group( 3 ) );
// add global parameters
// List globals = getGlobals();
// if ( globals != null ) {
// for ( Iterator iterator = globals.iterator(); iterator.hasNext(); ) {
// GlobalDescr global = (GlobalDescr) iterator.next();
// params.put( global.getIdentifier(),
// global.getType() );
// }
// }
String functionText = matcher.group( 4 );
props = getJavaCompletionProposals( documentOffset,
functionText,
prefix,
params,
false,
false);
filterProposalsOnPrefix( prefix,
props );
} else {
props = getPossibleProposals( viewer,
documentOffset,
backText,
prefix );
}
}
return props;
} catch ( Throwable t ) {
DroolsEclipsePlugin.log( t );
}
return null;
}
private Map extractParams(String params) {
Map result = new HashMap();
String[] parameters = StringUtils.split( params,
"," );
for ( int i = 0; i < parameters.length; i++ ) {
String[] typeAndName = StringUtils.split( parameters[i] );
if ( typeAndName.length == 2 ) {
result.put( typeAndName[1],
typeAndName[0] );
}
}
return result;
}
/*
* create and returns a java project based on the current editor input or returns null
*/
private IJavaProject getCurrentJavaProject() {
IEditorInput input = getEditor().getEditorInput();
if ( !(input instanceof IFileEditorInput) ) {
return null;
}
IProject project = ((IFileEditorInput) input).getFile().getProject();
IJavaProject javaProject = JavaCore.create( project );
return javaProject;
}
protected List getAllClassProposals(final String classNameStart,
final int documentOffset,
final String prefix) {
List result = new ArrayList();
IJavaProject javaProject = getCurrentJavaProject();
if ( javaProject == null ) {
return result;
}
CompletionProposalCollector collector = new CompletionProposalCollector( javaProject ) {
public void accept(CompletionProposal proposal) {
if ( proposal.getKind() == org.eclipse.jdt.core.CompletionProposal.PACKAGE_REF || proposal.getKind() == org.eclipse.jdt.core.CompletionProposal.TYPE_REF ) {
super.accept( proposal );
}
}
};
collector.acceptContext( new CompletionContext() );
try {
IEvaluationContext evalContext = javaProject.newEvaluationContext();
evalContext.codeComplete( classNameStart,
classNameStart.length(),
collector );
IJavaCompletionProposal[] proposals = collector.getJavaCompletionProposals();
for ( int i = 0; i < proposals.length; i++ ) {
if ( proposals[i] instanceof AbstractJavaCompletionProposal ) {
AbstractJavaCompletionProposal javaProposal = (AbstractJavaCompletionProposal) proposals[i];
int replacementOffset = documentOffset - (classNameStart.length() - javaProposal.getReplacementOffset());
javaProposal.setReplacementOffset( replacementOffset );
if ( javaProposal instanceof LazyJavaTypeCompletionProposal ) {
String completionPrefix = classNameStart.substring( classNameStart.length() - javaProposal.getReplacementLength() );
int dotIndex = completionPrefix.lastIndexOf( '.' );
// match up to the last dot in order to make higher level matching still work (camel case...)
if ( dotIndex != -1 ) {
javaProposal.setReplacementString( ((LazyJavaTypeCompletionProposal) javaProposal).getQualifiedTypeName() );
}
}
result.add( proposals[i] );
}
}
} catch ( Throwable t ) {
DroolsEclipsePlugin.log( t );
}
return result;
}
protected List<RuleCompletionProposal> getPossibleProposals(ITextViewer viewer,
int documentOffset,
String backText,
final String prefix) {
List<RuleCompletionProposal> list = new ArrayList<RuleCompletionProposal>();
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"rule",
NEW_RULE_TEMPLATE,
6 ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"import",
"import " ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"expander",
"expander " ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"global",
"global " ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"package",
"package " ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"query",
NEW_QUERY_TEMPLATE ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"function",
NEW_FUNCTION_TEMPLATE,
14 ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"template",
NEW_TEMPLATE_TEMPLATE,
9 ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"dialect \"java\"",
"dialect \"java\" " ) );
list.add( new RuleCompletionProposal( documentOffset - prefix.length(),
prefix.length(),
"dialect \"mvel\"",
"dialect \"mvel\" " ) );
filterProposalsOnPrefix( prefix,
list );
return list;
}
protected List<RuleCompletionProposal> getJavaCompletionProposals(final int documentOffset,
final String javaText,
final String prefix,
Map params) {
return getJavaCompletionProposals(documentOffset, javaText, prefix, params, true, false);
}
protected List<RuleCompletionProposal> getJavaCompletionProposals(final int documentOffset,
final String javaText,
final String prefix,
Map params,
boolean useDrools,
boolean useContext) {
final List<RuleCompletionProposal> list = new ArrayList<RuleCompletionProposal>();
requestJavaCompletionProposals( javaText,
prefix,
documentOffset,
params,
list,
useDrools,
useContext);
return list;
}
/*
* do we already have a completion for that string that would be either a local variable or a field?
*/
protected static boolean doesNotContainFieldCompletion(String completion,
List completions) {
if ( completion == null || completion.length() == 0 || completions == null ) {
return false;
}
for ( Iterator iter = completions.iterator(); iter.hasNext(); ) {
Object o = iter.next();
if ( o instanceof AbstractJavaCompletionProposal ) {
AbstractJavaCompletionProposal prop = (AbstractJavaCompletionProposal) o;
String content = prop.getReplacementString();
if ( completion.equals( content ) ) {
IJavaElement javaElement = prop.getJavaElement();
if ( javaElement instanceof ILocalVariable || javaElement instanceof IField ) {
return false;
}
}
}
}
return true;
}
protected void requestJavaCompletionProposals(final String javaText,
final String prefix,
final int documentOffset,
Map params,
Collection results) {
requestJavaCompletionProposals(javaText, prefix, documentOffset, params, results, true, false);
}
protected void requestJavaCompletionProposals(final String javaText,
final String prefix,
final int documentOffset,
Map params,
Collection results,
boolean useDrools,
boolean useContext) {
String javaTextWithoutPrefix = CompletionUtil.getTextWithoutPrefix( javaText,
prefix );
// boolean to filter default Object methods produced by code completion when in the beginning of a statement
boolean filterObjectMethods = false;
if ( "".equals( javaTextWithoutPrefix.trim() ) || CompletionUtil.START_OF_NEW_JAVA_STATEMENT.matcher( javaTextWithoutPrefix ).matches() ) {
filterObjectMethods = true;
}
IJavaProject javaProject = getCurrentJavaProject();
if ( javaProject == null ) {
return;
}
CompletionProposalCollector collector = new CompletionProposalCollector( javaProject );
collector.acceptContext( new CompletionContext() );
try {
IEvaluationContext evalContext = javaProject.newEvaluationContext();
List imports = getImports();
if ( imports != null && imports.size() > 0 ) {
evalContext.setImports( (String[]) imports.toArray( new String[imports.size()] ) );
}
StringBuffer javaTextWithParams = new StringBuffer();
Iterator iterator = params.entrySet().iterator();
while ( iterator.hasNext() ) {
Map.Entry entry = (Map.Entry) iterator.next();
// this does not seem to work, so adding variables manually
// evalContext.newVariable((String) entry.getValue(), (String) entry.getKey(), null);
javaTextWithParams.append( entry.getValue() + " " + entry.getKey() + ";\n" );
}
if (useDrools) {
javaTextWithParams.append( "org.drools.spi.KnowledgeHelper drools;" );
javaTextWithParams.append( "org.drools.runtime.rule.RuleContext kcontext;" );
}
if (useContext) {
javaTextWithParams.append( "org.drools.spi.ProcessContext context;" );
javaTextWithParams.append( "org.drools.runtime.process.ProcessContext kcontext;" );
}
javaTextWithParams.append( javaText );
String jtext = javaTextWithParams.toString();
String fixedText = new KnowledgeHelperFixer().fix( jtext );
evalContext.codeComplete( fixedText,
fixedText.length(),
collector );
IJavaCompletionProposal[] proposals = collector.getJavaCompletionProposals();
for ( int i = 0; i < proposals.length; i++ ) {
if ( proposals[i] instanceof AbstractJavaCompletionProposal ) {
AbstractJavaCompletionProposal javaProposal = (AbstractJavaCompletionProposal) proposals[i];
int replacementOffset = documentOffset - (fixedText.length() - javaProposal.getReplacementOffset());
javaProposal.setReplacementOffset( replacementOffset );
if ( javaProposal instanceof LazyJavaTypeCompletionProposal ) {
String completionPrefix = javaText.substring( javaText.length() - javaProposal.getReplacementLength() );
int dotIndex = completionPrefix.lastIndexOf( '.' );
// match up to the last dot in order to make higher level matching still work (camel case...)
if ( dotIndex != -1 ) {
javaProposal.setReplacementString( ((LazyJavaTypeCompletionProposal) javaProposal).getQualifiedTypeName() );
}
}
if ( !filterObjectMethods || !(proposals[i] instanceof JavaMethodCompletionProposal) ) {
results.add( proposals[i] );
}
}
}
} catch ( Throwable t ) {
DroolsEclipsePlugin.log( t );
}
}
protected String getPackage() {
if ( getEditor() instanceof DRLRuleEditor ) {
return ((DRLRuleEditor) getEditor()).getPackage();
}
return "";
}
protected List<String> getImports() {
if ( getEditor() instanceof DRLRuleEditor ) {
return ((DRLRuleEditor) getEditor()).getImports();
}
return Collections.EMPTY_LIST;
}
protected Set getUniqueImports() {
HashSet set = new HashSet();
set.addAll( getImports() );
return set;
}
protected List getFunctions() {
if ( getEditor() instanceof DRLRuleEditor ) {
return ((DRLRuleEditor) getEditor()).getFunctions();
}
return Collections.EMPTY_LIST;
}
protected Map getAttributes() {
if ( getEditor() instanceof DRLRuleEditor ) {
return ((DRLRuleEditor) getEditor()).getAttributes();
}
return Collections.EMPTY_MAP;
}
protected List<GlobalDescr> getGlobals() {
if ( getEditor() instanceof DRLRuleEditor ) {
return ((DRLRuleEditor) getEditor()).getGlobals();
}
return Collections.EMPTY_LIST;
}
protected List<String> getClassesInPackage() {
if ( getEditor() instanceof DRLRuleEditor ) {
return ((DRLRuleEditor) getEditor()).getClassesInPackage();
}
return Collections.EMPTY_LIST;
}
}