001: package org.drools.eclipse.editors.completion;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.Collections;
006: import java.util.HashMap;
007: import java.util.HashSet;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.Map;
011: import java.util.Set;
012: import java.util.regex.Matcher;
013: import java.util.regex.Pattern;
014:
015: import org.drools.eclipse.DroolsEclipsePlugin;
016: import org.drools.eclipse.DroolsPluginImages;
017: import org.drools.eclipse.editors.AbstractRuleEditor;
018: import org.drools.eclipse.editors.DRLRuleEditor;
019: import org.drools.lang.descr.FactTemplateDescr;
020: import org.drools.lang.descr.GlobalDescr;
021: import org.drools.util.StringUtils;
022: import org.eclipse.core.resources.IProject;
023: import org.eclipse.jdt.core.CompletionContext;
024: import org.eclipse.jdt.core.CompletionProposal;
025: import org.eclipse.jdt.core.IField;
026: import org.eclipse.jdt.core.IJavaElement;
027: import org.eclipse.jdt.core.IJavaProject;
028: import org.eclipse.jdt.core.ILocalVariable;
029: import org.eclipse.jdt.core.JavaCore;
030: import org.eclipse.jdt.core.eval.IEvaluationContext;
031: import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal;
032: import org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal;
033: import org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal;
034: import org.eclipse.jdt.internal.ui.text.java.LazyJavaTypeCompletionProposal;
035: import org.eclipse.jdt.ui.text.java.CompletionProposalCollector;
036: import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
037: import org.eclipse.jface.text.IDocument;
038: import org.eclipse.jface.text.ITextViewer;
039: import org.eclipse.swt.graphics.Image;
040: import org.eclipse.ui.IEditorInput;
041: import org.eclipse.ui.IFileEditorInput;
042:
043: /**
044: * This is the basic completion processor that is used when the editor is outside of a rule block
045: * partition.
046: * The provides the content assistance for basic rule assembly stuff.
047: *
048: * This processor will also read behind the current editing position, to provide some context to
049: * help provide the pop up list.
050: *
051: * @author Michael Neale, Kris Verlaenen
052: */
053: public class DefaultCompletionProcessor extends
054: AbstractCompletionProcessor {
055:
056: private static final String NEW_RULE_TEMPLATE = "rule \"new rule\""
057: + System.getProperty("line.separator") + "\twhen"
058: + System.getProperty("line.separator") + "\t\t"
059: + System.getProperty("line.separator") + "\tthen"
060: + System.getProperty("line.separator") + "\t\t"
061: + System.getProperty("line.separator") + "end";
062: private static final String NEW_QUERY_TEMPLATE = "query \"query name\""
063: + System.getProperty("line.separator")
064: + "\t#conditions"
065: + System.getProperty("line.separator") + "end";
066: private static final String NEW_FUNCTION_TEMPLATE = "function void yourFunction(Type arg) {"
067: + System.getProperty("line.separator")
068: + "\t/* code goes here*/"
069: + System.getProperty("line.separator") + "}";
070: private static final String NEW_TEMPLATE_TEMPLATE = "template Name"
071: + System.getProperty("line.separator") + "\t"
072: + System.getProperty("line.separator") + "end";
073: private static final Pattern IMPORT_PATTERN = Pattern.compile(
074: ".*\n\\W*import\\W[^;\\s]*", Pattern.DOTALL);
075: // TODO: doesn't work for { inside functions
076: private static final Pattern FUNCTION_PATTERN = Pattern
077: .compile(
078: ".*\n\\W*function\\s+(\\S+)\\s+(\\S+)\\s*\\(([^\\)]*)\\)\\s*\\{([^\\}]*)",
079: Pattern.DOTALL);
080: protected static final Image VARIABLE_ICON = DroolsPluginImages
081: .getImage(DroolsPluginImages.VARIABLE);
082: protected static final Image METHOD_ICON = DroolsPluginImages
083: .getImage(DroolsPluginImages.METHOD);
084: protected static final Image CLASS_ICON = DroolsPluginImages
085: .getImage(DroolsPluginImages.CLASS);
086:
087: protected static final Pattern START_OF_NEW_JAVA_STATEMENT = Pattern
088: .compile(".*[;{}]\\s*", Pattern.DOTALL);
089:
090: public DefaultCompletionProcessor(AbstractRuleEditor editor) {
091: super (editor);
092: }
093:
094: protected List getCompletionProposals(ITextViewer viewer,
095: int documentOffset) {
096: try {
097: IDocument doc = viewer.getDocument();
098: String backText = readBackwards(documentOffset, doc);
099:
100: String prefix = CompletionUtil.stripLastWord(backText);
101:
102: List props = null;
103: Matcher matcher = IMPORT_PATTERN.matcher(backText);
104: if (matcher.matches()) {
105: String classNameStart = backText.substring(backText
106: .lastIndexOf("import") + 7);
107: props = getAllClassProposals(classNameStart,
108: documentOffset, prefix);
109: } else {
110: matcher = FUNCTION_PATTERN.matcher(backText);
111: if (matcher.matches()) {
112: // extract function parameters
113: Map params = extractParams(matcher.group(3));
114: // add global parameters
115: List globals = getGlobals();
116: if (globals != null) {
117: for (Iterator iterator = globals.iterator(); iterator
118: .hasNext();) {
119: GlobalDescr global = (GlobalDescr) iterator
120: .next();
121: params.put(global.getIdentifier(), global
122: .getType());
123: }
124: }
125: String functionText = matcher.group(4);
126: props = getJavaCompletionProposals(documentOffset,
127: functionText, prefix, params);
128: filterProposalsOnPrefix(prefix, props);
129: } else {
130: props = getPossibleProposals(viewer,
131: documentOffset, backText, prefix);
132: }
133: }
134: return props;
135: } catch (Throwable t) {
136: DroolsEclipsePlugin.log(t);
137: }
138: return null;
139: }
140:
141: private Map extractParams(String params) {
142: Map result = new HashMap();
143: String[] parameters = StringUtils.split(params, ",");
144: for (int i = 0; i < parameters.length; i++) {
145: String[] typeAndName = StringUtils.split(parameters[i]);
146: if (typeAndName.length == 2) {
147: result.put(typeAndName[1], typeAndName[0]);
148: }
149: }
150: return result;
151: }
152:
153: /*
154: * create and returns a java project based on the current editor input or returns null
155: */
156: private IJavaProject getCurrentJavaProject() {
157: IEditorInput input = getEditor().getEditorInput();
158: if (!(input instanceof IFileEditorInput)) {
159: return null;
160: }
161: IProject project = ((IFileEditorInput) input).getFile()
162: .getProject();
163: IJavaProject javaProject = JavaCore.create(project);
164: return javaProject;
165: }
166:
167: private List getAllClassProposals(final String classNameStart,
168: final int documentOffset, final String prefix) {
169: List result = new ArrayList();
170: IJavaProject javaProject = getCurrentJavaProject();
171: if (javaProject == null) {
172: return result;
173: }
174: CompletionProposalCollector collector = new CompletionProposalCollector(
175: javaProject) {
176: public void accept(CompletionProposal proposal) {
177: if (proposal.getKind() == org.eclipse.jdt.core.CompletionProposal.PACKAGE_REF
178: || proposal.getKind() == org.eclipse.jdt.core.CompletionProposal.TYPE_REF) {
179: super .accept(proposal);
180: }
181: }
182: };
183: collector.acceptContext(new CompletionContext());
184: try {
185: IEvaluationContext evalContext = javaProject
186: .newEvaluationContext();
187: evalContext.codeComplete(classNameStart, classNameStart
188: .length(), collector);
189: IJavaCompletionProposal[] proposals = collector
190: .getJavaCompletionProposals();
191: for (int i = 0; i < proposals.length; i++) {
192: if (proposals[i] instanceof AbstractJavaCompletionProposal) {
193: AbstractJavaCompletionProposal javaProposal = (AbstractJavaCompletionProposal) proposals[i];
194: int replacementOffset = documentOffset
195: - (classNameStart.length() - javaProposal
196: .getReplacementOffset());
197: javaProposal
198: .setReplacementOffset(replacementOffset);
199: if (javaProposal instanceof LazyJavaTypeCompletionProposal) {
200: String completionPrefix = classNameStart
201: .substring(classNameStart.length()
202: - javaProposal
203: .getReplacementLength());
204: int dotIndex = completionPrefix
205: .lastIndexOf('.');
206: // match up to the last dot in order to make higher level matching still work (camel case...)
207: if (dotIndex != -1) {
208: javaProposal
209: .setReplacementString(((LazyJavaTypeCompletionProposal) javaProposal)
210: .getQualifiedTypeName());
211: }
212: }
213: result.add(proposals[i]);
214: }
215: }
216: } catch (Throwable t) {
217: DroolsEclipsePlugin.log(t);
218: }
219: return result;
220: }
221:
222: protected List getPossibleProposals(ITextViewer viewer,
223: int documentOffset, String backText, final String prefix) {
224: List list = new ArrayList();
225: list.add(new RuleCompletionProposal(documentOffset
226: - prefix.length(), prefix.length(), "rule",
227: NEW_RULE_TEMPLATE, 6));
228: list
229: .add(new RuleCompletionProposal(documentOffset
230: - prefix.length(), prefix.length(), "import",
231: "import "));
232: list.add(new RuleCompletionProposal(documentOffset
233: - prefix.length(), prefix.length(), "expander",
234: "expander "));
235: list
236: .add(new RuleCompletionProposal(documentOffset
237: - prefix.length(), prefix.length(), "global",
238: "global "));
239: list.add(new RuleCompletionProposal(documentOffset
240: - prefix.length(), prefix.length(), "package",
241: "package "));
242: list.add(new RuleCompletionProposal(documentOffset
243: - prefix.length(), prefix.length(), "query",
244: NEW_QUERY_TEMPLATE));
245: list.add(new RuleCompletionProposal(documentOffset
246: - prefix.length(), prefix.length(), "function",
247: NEW_FUNCTION_TEMPLATE, 14));
248: list.add(new RuleCompletionProposal(documentOffset
249: - prefix.length(), prefix.length(), "template",
250: NEW_TEMPLATE_TEMPLATE, 9));
251: list.add(new RuleCompletionProposal(documentOffset
252: - prefix.length(), prefix.length(), "dialect \"java\"",
253: "dialect \"java\" "));
254: list.add(new RuleCompletionProposal(documentOffset
255: - prefix.length(), prefix.length(), "dialect \"mvel\"",
256: "dialect \"mvel\" "));
257: filterProposalsOnPrefix(prefix, list);
258: return list;
259: }
260:
261: protected List getJavaCompletionProposals(final int documentOffset,
262: final String javaText, final String prefix, Map params) {
263: final List list = new ArrayList();
264: requestJavaCompletionProposals(javaText, prefix,
265: documentOffset, params, list);
266: return list;
267: }
268:
269: /**
270: * @return a list of "MVELified" RuleCompletionProposal. That list contains only unique proposal based on
271: * the overrriden equals in {@link RuleCompletionProposal} to avoid the situation when several
272: * accessors can exist for one property. for that case we want to keep only one proposal.
273: */
274: protected Collection getJavaMvelCompletionProposals(
275: final int documentOffset, final String javaText,
276: final String prefix, Map params) {
277: final List list = new ArrayList();
278: requestJavaCompletionProposals(javaText, prefix,
279: documentOffset, params, list);
280:
281: Collection mvelList = mvelifyProposals(list);
282: return mvelList;
283: }
284:
285: /*
286: * Filters accessor method proposals to replace them with their mvel expression equivalent
287: * For instance a completion for getStatus() would be replaced by a completion for status
288: */
289: private static Collection mvelifyProposals(List list) {
290: final Collection set = new HashSet();
291:
292: for (Iterator iter = list.iterator(); iter.hasNext();) {
293: Object o = iter.next();
294: if (o instanceof JavaMethodCompletionProposal) {
295: LazyJavaCompletionProposal javaProposal = (LazyJavaCompletionProposal) o;
296: //TODO: FIXME: this is very fragile ass it uses reflection to access the private completion field.
297: //Yet this is needed to do mvel filtering based on the method signtures, IF we use the richer JDT completion
298: Object field = ReflectionUtils.getField(o, "fProposal");
299: if (field != null
300: && field instanceof CompletionProposal) {
301: CompletionProposal proposal = (CompletionProposal) field;
302:
303: String completion = new String(proposal
304: .getCompletion());
305:
306: // get the eventual property name for that method name and signature
307: String propertyOrMethodName = CompletionUtil
308: .getPropertyName(completion, proposal
309: .getSignature());
310: //if we got a proeprty name that differs from the orginal method name
311: //, this is a a bean accessor
312: boolean isAccessor = !completion
313: .equals(propertyOrMethodName);
314:
315: // is the completion for a bean accessor? and do we have already some relevant completion?
316: if (isAccessor
317: && doesNotContainFieldCompletion(
318: propertyOrMethodName, list)) {
319: //TODO: craft a better JDTish display name
320: RuleCompletionProposal prop = new RuleCompletionProposal(
321: javaProposal.getReplacementOffset(),
322: javaProposal.getReplacementLength(),
323: propertyOrMethodName);
324: prop
325: .setImage(DefaultCompletionProcessor.VARIABLE_ICON);
326: set.add(prop);
327:
328: } else {
329: set.add(o);
330: }
331: }
332:
333: } else {
334: //add other proposals as is
335: set.add(o);
336: }
337: }
338: return set;
339: }
340:
341: /*
342: * do we already have a completion for that string that would be either a local variable or a field?
343: */
344: private static boolean doesNotContainFieldCompletion(
345: String completion, List completions) {
346: if (completion == null || completion.length() == 0
347: || completions == null) {
348: return false;
349: }
350: for (Iterator iter = completions.iterator(); iter.hasNext();) {
351: Object o = iter.next();
352: if (o instanceof AbstractJavaCompletionProposal) {
353: AbstractJavaCompletionProposal prop = (AbstractJavaCompletionProposal) o;
354: String content = prop.getReplacementString();
355: if (completion.equals(content)) {
356: IJavaElement javaElement = prop.getJavaElement();
357: if (javaElement instanceof ILocalVariable
358: || javaElement instanceof IField) {
359: return false;
360: }
361: }
362: }
363: }
364: return true;
365: }
366:
367: protected void requestJavaCompletionProposals(
368: final String javaText, final String prefix,
369: final int documentOffset, Map params, Collection results) {
370:
371: String javaTextWithoutPrefix = getTextWithoutPrefix(javaText,
372: prefix);
373: // boolean to filter default Object methods produced by code completion when in the beginning of a statement
374: boolean filterObjectMethods = false;
375: if ("".equals(javaTextWithoutPrefix.trim())
376: || START_OF_NEW_JAVA_STATEMENT.matcher(
377: javaTextWithoutPrefix).matches()) {
378: filterObjectMethods = true;
379: }
380: IJavaProject javaProject = getCurrentJavaProject();
381: if (javaProject == null) {
382: return;
383: }
384:
385: CompletionProposalCollector collector = new CompletionProposalCollector(
386: javaProject);
387: collector.acceptContext(new CompletionContext());
388:
389: try {
390: IEvaluationContext evalContext = javaProject
391: .newEvaluationContext();
392: List imports = getImports();
393: if (imports != null && imports.size() > 0) {
394: evalContext.setImports((String[]) imports
395: .toArray(new String[imports.size()]));
396: }
397: StringBuffer javaTextWithParams = new StringBuffer();
398: Iterator iterator = params.entrySet().iterator();
399: while (iterator.hasNext()) {
400: Map.Entry entry = (Map.Entry) iterator.next();
401: // this does not seem to work, so adding variables manually
402: // evalContext.newVariable((String) entry.getValue(), (String) entry.getKey(), null);
403: javaTextWithParams.append(entry.getValue() + " "
404: + entry.getKey() + ";\n");
405: }
406: javaTextWithParams
407: .append("org.drools.spi.KnowledgeHelper drools;");
408: javaTextWithParams.append(javaText);
409: String text = javaTextWithParams.toString();
410: evalContext.codeComplete(text, text.length(), collector);
411: IJavaCompletionProposal[] proposals = collector
412: .getJavaCompletionProposals();
413: for (int i = 0; i < proposals.length; i++) {
414: if (proposals[i] instanceof AbstractJavaCompletionProposal) {
415: AbstractJavaCompletionProposal javaProposal = (AbstractJavaCompletionProposal) proposals[i];
416: int replacementOffset = documentOffset
417: - (text.length() - javaProposal
418: .getReplacementOffset());
419: javaProposal
420: .setReplacementOffset(replacementOffset);
421: if (javaProposal instanceof LazyJavaTypeCompletionProposal) {
422: String completionPrefix = javaText
423: .substring(javaText.length()
424: - javaProposal
425: .getReplacementLength());
426: int dotIndex = completionPrefix
427: .lastIndexOf('.');
428: // match up to the last dot in order to make higher level matching still work (camel case...)
429: if (dotIndex != -1) {
430: javaProposal
431: .setReplacementString(((LazyJavaTypeCompletionProposal) javaProposal)
432: .getQualifiedTypeName());
433: }
434: }
435: if (!filterObjectMethods
436: || !(proposals[i] instanceof JavaMethodCompletionProposal)) {
437: results.add(proposals[i]);
438: }
439: }
440: }
441: } catch (Throwable t) {
442: DroolsEclipsePlugin.log(t);
443: }
444: }
445:
446: public static String getTextWithoutPrefix(final String javaText,
447: final String prefix) {
448: int endIndex = javaText.length() - prefix.length();
449: String javaTextWithoutPrefix = javaText;
450: //javaText can be an empty string.
451: if (endIndex > 0) {
452: javaTextWithoutPrefix = javaText.substring(0, endIndex);
453: }
454: return javaTextWithoutPrefix;
455: }
456:
457: protected String getPackage() {
458: if (getEditor() instanceof DRLRuleEditor) {
459: return ((DRLRuleEditor) getEditor()).getPackage();
460: }
461: return "";
462: }
463:
464: protected List getImports() {
465: if (getEditor() instanceof DRLRuleEditor) {
466: return ((DRLRuleEditor) getEditor()).getImports();
467: }
468: return Collections.EMPTY_LIST;
469: }
470:
471: protected Set getUniqueImports() {
472: HashSet set = new HashSet();
473: set.addAll(getImports());
474: return set;
475: }
476:
477: protected List getFunctions() {
478: if (getEditor() instanceof DRLRuleEditor) {
479: return ((DRLRuleEditor) getEditor()).getFunctions();
480: }
481: return Collections.EMPTY_LIST;
482: }
483:
484: protected Map getAttributes() {
485: if (getEditor() instanceof DRLRuleEditor) {
486: return ((DRLRuleEditor) getEditor()).getAttributes();
487: }
488: return Collections.EMPTY_MAP;
489: }
490:
491: protected Set getTemplates() {
492: if (getEditor() instanceof DRLRuleEditor) {
493: return ((DRLRuleEditor) getEditor()).getTemplates();
494: }
495: return Collections.EMPTY_SET;
496: }
497:
498: protected FactTemplateDescr getTemplate(String name) {
499: if (getEditor() instanceof DRLRuleEditor) {
500: return ((DRLRuleEditor) getEditor()).getTemplate(name);
501: }
502: return null;
503: }
504:
505: protected List getGlobals() {
506: if (getEditor() instanceof DRLRuleEditor) {
507: return ((DRLRuleEditor) getEditor()).getGlobals();
508: }
509: return Collections.EMPTY_LIST;
510: }
511:
512: protected List getClassesInPackage() {
513: if (getEditor() instanceof DRLRuleEditor) {
514: return ((DRLRuleEditor) getEditor()).getClassesInPackage();
515: }
516: return Collections.EMPTY_LIST;
517: }
518:
519: protected boolean isStartOfDialectExpression(String text) {
520: return "".equals(text.trim())
521: || START_OF_NEW_JAVA_STATEMENT.matcher(text).matches();
522: }
523: }
|