Java tutorial
/* * 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.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.Set; import java.util.regex.Matcher; import org.drools.base.ClassTypeResolver; import org.drools.compiler.PackageBuilderConfiguration; import org.drools.core.util.asm.ClassFieldInspector; import org.drools.eclipse.DRLInfo; import org.drools.eclipse.DroolsEclipsePlugin; import org.drools.eclipse.DroolsPluginImages; import org.drools.eclipse.DRLInfo.RuleInfo; import org.drools.eclipse.editors.AbstractRuleEditor; import org.drools.eclipse.util.ProjectClassLoader; import org.drools.lang.Location; import org.drools.lang.descr.FactTemplateDescr; import org.drools.lang.descr.FieldTemplateDescr; import org.drools.lang.descr.GlobalDescr; import org.drools.rule.builder.dialect.mvel.MVELConsequenceBuilder; import org.drools.rule.builder.dialect.mvel.MVELDialect; import org.drools.runtime.rule.RuleContext; import org.drools.spi.KnowledgeHelper; 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.mvel2.ParserContext; import org.mvel2.compiler.CompiledExpression; import org.mvel2.compiler.ExpressionCompiler; 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 getCompletionProposals(ITextViewer viewer, int documentOffset) { try { final List list = new ArrayList(); 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 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<RuleCompletionProposal> 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); } } iterator = getTemplates().iterator(); while (iterator.hasNext()) { String name = (String) iterator.next(); RuleCompletionProposal p = new RuleCompletionProposal(documentOffset - prefix.length(), prefix.length(), name, name + "( )", name.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) { boolean isTemplate = addFactTemplatePropertyProposals(documentOffset, prefix, className, list); if (!isTemplate) { 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 types = inspector.getFieldTypes(); Iterator iterator2 = inspector.getFieldNames().keySet().iterator(); while (iterator2.hasNext()) { String name = (String) iterator2.next(); p = new RuleCompletionProposal(documentOffset - prefix.length(), prefix.length(), name, name + " "); p.setImage(METHOD_ICON); list.add(p); Class type = (Class) 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 result = new HashMap(); addRuleParameters(result, context.getRuleParameters()); Iterator iterator2 = result.entrySet().iterator(); while (iterator2.hasNext()) { Map.Entry entry = (Map.Entry) iterator2.next(); String paramName = (String) entry.getKey(); String paramType = (String) 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)); PackageBuilderConfiguration config = new PackageBuilderConfiguration( ProjectClassLoader.getProjectClassLoader(getEditor()), null); Map accumulateFunctions = config.getAccumulateFunctionsMap(); for (iterator2 = accumulateFunctions.keySet().iterator(); iterator2.hasNext();) { String accumulateFunction = (String) iterator2.next(); 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 functions = getFunctions(); iterator = functions.iterator(); while (iterator.hasNext()) { String name = (String) 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) { FactTemplateDescr template = getTemplate(className); if (template != null) { Iterator iterator = template.getFields().iterator(); while (iterator.hasNext()) { FieldTemplateDescr field = (FieldTemplateDescr) iterator.next(); if (propertyName.equals(field.getName())) { String type = field.getClassType(); if (isPrimitiveType(type)) { return type; } ClassTypeResolver resolver = new ClassTypeResolver(getUniqueImports(), ProjectClassLoader.getProjectClassLoader(getEditor())); try { Class clazz = resolver.resolveType(type); if (clazz != null) { return clazz.getName(); } } catch (ClassNotFoundException exc) { DroolsEclipsePlugin.log(exc); } } } // if not found, return null } else { 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 = (Class) 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 getRuleParameters(String backText) { Map result = new HashMap(); // add globals List globals = getGlobals(); if (globals != null) { for (Iterator iterator = globals.iterator(); iterator.hasNext();) { GlobalDescr global = (GlobalDescr) iterator.next(); 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 list, int documentOffset, String prefix) { Iterator iterator; RuleCompletionProposal prop; List functions = getFunctions(); iterator = functions.iterator(); while (iterator.hasNext()) { String name = (String) 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 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 list, int documentOffset, String prefix, String backText, String consequence) { list.addAll(getJavaCompletionProposals(documentOffset, consequence, prefix, getRuleParameters(backText))); } private void addRHSMvelCompletionProposals(List list, final int documentOffset, String prefix, String backText, String consequence, boolean expressionStart) { Collection mvelCompletionProposals = getMvelCompletionProposals(consequence, documentOffset, prefix, getRuleParameters(backText), backText, expressionStart); list.addAll(mvelCompletionProposals); } private Collection getMvelCompletionProposals(final String consequenceBackText, final int documentOffset, final String prefix, Map params, String ruleBackText, boolean startOfExpression) { final Set proposals = new HashSet(); 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 Map variables = previousExprContext.getContext().getVariables(); Map 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 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 jdtProps = getJavaCompletionProposals(documentOffset, prefix, prefix, params); proposals.addAll(jdtProps); } catch (Throwable e) { DroolsEclipsePlugin.log(e); } Set uniqueProposals = new HashSet(); addAllNewProposals(uniqueProposals, proposals); return uniqueProposals; } private Map getResolvedMvelInputs(Map params) { ClassTypeResolver resolver = new ClassTypeResolver(getUniqueImports(), ProjectClassLoader.getProjectClassLoader(getEditor())); Map resolved = new HashMap(); for (Iterator iter = params.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); String inputType = (String) 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 initialContext; private Class returnedType; private boolean staticFlag; public ParserContext getContext() { if (getExpression() != null) { if (getExpression().getParserContext() != null) { return getExpression().getParserContext(); } } return getInitialContext(); } void setExpression(CompiledExpression expression) { this.expression = expression; } CompiledExpression getExpression() { return expression; } void setInitialContext(ParserContext initialContext) { this.initialContext = initialContext; } ParserContext getInitialContext() { return initialContext; } 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(Map params, DRLInfo drlInfo, String mvel) { String macroMvel = 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.setInitialContext(initialContext); try { ExpressionCompiler compiler = new ExpressionCompiler(macroMvel); CompiledExpression expression = compiler.compile(initialContext); mCon.setExpression(expression); ParserContext compilationContext = compiler.getParserContextState(); Class lastType = expression.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(macroMvel, compilationContext).analyze(); } if (lastType == null) { lastType = Object.class; } mCon.setReturnedType(lastType); } catch (Exception e) { //do nothing while doing completion. } return mCon; } private static ParserContext createInitialContext(Map params, String qualifiedName, MVELDialect dialect) { final ParserContext context = new ParserContext(dialect.getImports(), null, qualifiedName); if (dialect.getPackgeImports() != null) { for (Iterator it = dialect.getPackgeImports().iterator(); it.hasNext();) { String packageImport = (String) it.next(); 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) { MVELConsequenceBuilder builder = new MVELConsequenceBuilder(); String macrosProcessedCompilableConsequence = builder.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 getMvelInstanceCompletionsFromJDT(final int documentOffset, final String prefix, Map 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 list1 = new ArrayList(); requestJavaCompletionProposals(javaText, prefix, documentOffset, params, list1); final List list = list1; Collection mvelList = RuleCompletionProcessor.mvelifyProposals(list, settersOnly); return mvelList; } /* * Completions for static Class members */ private Collection getMvelClassCompletionsFromJDT(final int documentOffset, final String prefix, Map 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 list1 = new ArrayList(); requestJavaCompletionProposals(javaText, prefix, documentOffset, params, list1); final List list = list1; Collection mvelList = RuleCompletionProcessor.mvelifyProposals(list, false); return mvelList; } private static void addMvelCompletions(final Collection proposals, int documentOffset, String prefix, Map inputs) { Set newProposals = new HashSet(); for (Iterator iter = inputs.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); String prop = (String) entry.getKey(); Class type = (Class) 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 proposals, final Collection newProposals) { for (Iterator iter = newProposals.iterator(); iter.hasNext();) { ICompletionProposal newProp = (ICompletionProposal) iter.next(); 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 proposals, String newProposal) { for (Iterator iter = proposals.iterator(); iter.hasNext();) { ICompletionProposal prop = (ICompletionProposal) iter.next(); 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 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)); } private boolean addFactTemplatePropertyProposals(int documentOffset, String prefix, String templateName, List list) { FactTemplateDescr descr = getTemplate(templateName); if (descr == null) { return false; } Iterator iterator = descr.getFields().iterator(); while (iterator.hasNext()) { FieldTemplateDescr field = (FieldTemplateDescr) iterator.next(); String fieldName = field.getName(); RuleCompletionProposal p = new RuleCompletionProposal(documentOffset - prefix.length(), prefix.length(), fieldName, fieldName + " "); p.setImage(METHOD_ICON); list.add(p); } return true; } /* * 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 mvelifyProposals(List list, boolean settersOnly) { final Collection set = new HashSet(); for (Iterator iter = list.iterator(); iter.hasNext();) { Object o = iter.next(); 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 set, Object 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 list, boolean settersOnly, final Collection set, Object 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); } } } }