/* * 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.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.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.drools.compiler.lang.descr.GlobalDescr; import org.drools.compiler.rule.builder.dialect.java.KnowledgeHelperFixer; 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.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.jface.text.contentassist.ICompletionProposal; 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<ICompletionProposal> getCompletionProposals(ITextViewer viewer, int documentOffset) { try { IDocument doc = viewer.getDocument(); String backText = readBackwards( documentOffset, doc ); String prefix = CompletionUtil.stripLastWord( backText ); List<ICompletionProposal> 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<String, String> 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<String, String> extractParams(String params) { Map<String, String> result = new HashMap<String, String>(); String[] parameters = params.split( "," ); for ( int i = 0; i < parameters.length; i++ ) { String[] typeAndName = parameters[i].split( " " ); 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<ICompletionProposal> getAllClassProposals(final String classNameStart, final int documentOffset, final String prefix) { List<ICompletionProposal> result = new ArrayList<ICompletionProposal>(); 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<ICompletionProposal> getPossibleProposals(ITextViewer viewer, int documentOffset, String backText, final String prefix) { List<ICompletionProposal> list = new ArrayList<ICompletionProposal>(); 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<ICompletionProposal> getJavaCompletionProposals(final int documentOffset, final String javaText, final String prefix, Map<String, String> params) { return getJavaCompletionProposals(documentOffset, javaText, prefix, params, true, false); } protected List<ICompletionProposal> getJavaCompletionProposals(final int documentOffset, final String javaText, final String prefix, Map<String, String> params, boolean useDrools, boolean useContext) { final List<ICompletionProposal> list = new ArrayList<ICompletionProposal>(); 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<ICompletionProposal> completions) { if ( completion == null || completion.length() == 0 || completions == null ) { return false; } for ( Iterator<ICompletionProposal> 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<String, String> params, Collection<ICompletionProposal> results) { requestJavaCompletionProposals(javaText, prefix, documentOffset, params, results, true, false); } protected void requestJavaCompletionProposals(final String javaText, final String prefix, final int documentOffset, Map<String, String> params, Collection<ICompletionProposal> 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<String> imports = getImports(); if ( imports != null && imports.size() > 0 ) { evalContext.setImports(imports.toArray(new String[imports.size()])); } StringBuffer javaTextWithParams = new StringBuffer(); for (Entry<String, String> entry : params.entrySet()) { // 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.kie.api.runtime.rule.RuleContext kcontext;" ); } if (useContext) { javaTextWithParams.append( "org.kie.api.runtime.process.ProcessContext kcontext;" ); } javaTextWithParams.append( javaText ); String jtext = javaTextWithParams.toString(); String fixedText = 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.emptyList(); } protected Set<String> getUniqueImports() { HashSet<String> set = new HashSet<String>(); set.addAll( getImports() ); return set; } protected List<String> getFunctions() { if ( getEditor() instanceof DRLRuleEditor ) { return ((DRLRuleEditor) getEditor()).getFunctions(); } return Collections.emptyList(); } protected Map<String, String> getAttributes() { if ( getEditor() instanceof DRLRuleEditor ) { return ((DRLRuleEditor) getEditor()).getAttributes(); } return Collections.emptyMap(); } protected List<GlobalDescr> getGlobals() { if ( getEditor() instanceof DRLRuleEditor ) { return ((DRLRuleEditor) getEditor()).getGlobals(); } return Collections.emptyList(); } protected List<String> getClassesInPackage() { if ( getEditor() instanceof DRLRuleEditor ) { return ((DRLRuleEditor) getEditor()).getClassesInPackage(); } return Collections.emptyList(); } }