diff --git a/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java b/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java index c110eb24..44704a22 100644 --- a/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java +++ b/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java @@ -14,6 +14,7 @@ import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Map; +import java.util.WeakHashMap; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; @@ -79,7 +80,8 @@ public void addLanguageSupport(String style, String lsClassName) { */ private void createSupportMap() { - styleToSupport = new HashMap(); + // use WeakHashMap so language supports not needed any more can be GC'd + styleToSupport = new WeakHashMap(); styleToSupportClass = new HashMap(); String prefix = "org.fife.rsta.ac."; @@ -149,7 +151,8 @@ public LanguageSupport getSupportFor(String style) { } styleToSupport.put(style, support); // Always remove from classes to load, so we don't try again - styleToSupportClass.remove(style); + // don't do this, since the WeakHashMap can loose the reference, so we need to load it again +// styleToSupportClass.remove(style); } } diff --git a/src/main/java/org/fife/rsta/ac/java/ClassCompletion.java b/src/main/java/org/fife/rsta/ac/java/ClassCompletion.java index c25394a3..e20a5f07 100644 --- a/src/main/java/org/fife/rsta/ac/java/ClassCompletion.java +++ b/src/main/java/org/fife/rsta/ac/java/ClassCompletion.java @@ -41,6 +41,11 @@ public ClassCompletion(CompletionProvider provider, ClassFile cf) { this.cf = cf; } + public ClassCompletion(CompletionProvider provider, ClassFile cf, String replacementText) { + super(provider, replacementText); + this.cf = cf; + } + /* * Fixed error when comparing classes of the same name, which did not allow diff --git a/src/main/java/org/fife/rsta/ac/java/ExternalMemberClickedListener.java b/src/main/java/org/fife/rsta/ac/java/ExternalMemberClickedListener.java new file mode 100644 index 00000000..7c39991a --- /dev/null +++ b/src/main/java/org/fife/rsta/ac/java/ExternalMemberClickedListener.java @@ -0,0 +1,31 @@ +package org.fife.rsta.ac.java; + +import org.fife.rsta.ac.java.classreader.FieldInfo; +import org.fife.rsta.ac.java.classreader.MethodInfo; +import org.fife.rsta.ac.java.rjc.ast.*; + +/** + * @since 2017.03.06. + */ +public interface ExternalMemberClickedListener +{ + /** + * Indicates user clicked external class + * @param className the fully qualified class name clicked + */ + void openClass(String className); + + /** + * Indicates user clicked a method in an external class + * @param className the fully qualified class name containing the method + * @param methodInfo the MethodInfo structure holding information about clicked method + */ + void gotoMethodInClass(String className, MethodInfo methodInfo); + + /** + * Indicates user clicked a field in an external class + * @param className the fully qualified class name containing the field + * @param fieldInfo the FieldInfo structure holding information about clicked field + */ + void gotoFieldInClass(String className, FieldInfo fieldInfo); +} diff --git a/src/main/java/org/fife/rsta/ac/java/FieldCompletion.java b/src/main/java/org/fife/rsta/ac/java/FieldCompletion.java index 294b1d0d..760bc882 100644 --- a/src/main/java/org/fife/rsta/ac/java/FieldCompletion.java +++ b/src/main/java/org/fife/rsta/ac/java/FieldCompletion.java @@ -74,10 +74,133 @@ public boolean equals(Object obj) { ((FieldCompletion)obj).getSignature().equals(getSignature()); } + /** + * Used to add "this" with the class type to the completion list + * @param provider + * @param type + * @return + */ + public static FieldCompletion createThisCompletion(CompletionProvider provider, final Type type) { + FieldCompletion fc = new FieldCompletion(provider, "this"); + fc.data = new Data() { + @Override + public String getEnclosingClassName(boolean fullyQualified) { + return type.getName(fullyQualified); + } + + @Override + public String getIcon() { + return IconFactory.FIELD_PUBLIC_ICON; + } + + @Override + public String getSignature() { + return "this"; + } + + @Override + public String getSummary() { + return null; + } + + @Override + public String getType() { + return type.getName(false); + } + + @Override + public boolean isConstructor() { + return false; + } + + @Override + public boolean isDeprecated() { + return false; + } + + @Override + public boolean isAbstract() { + return false; + } + + @Override + public boolean isFinal() { + return false; + } + + @Override + public boolean isStatic() { + return false; + } + }; + return fc; + } + + /** + * Used to add "super" with the class type to the completion list + * @param provider + * @param type + * @return + */ + public static FieldCompletion createSuperCompletion(CompletionProvider provider, final Type type) { + FieldCompletion fc = new FieldCompletion(provider, "super"); + fc.data = new Data() { + @Override + public String getEnclosingClassName(boolean fullyQualified) { + return type.getName(fullyQualified); + } + + @Override + public String getIcon() { + return IconFactory.FIELD_PROTECTED_ICON; + } + + @Override + public String getSignature() { + return "super"; + } + + @Override + public String getSummary() { + return null; + } + + @Override + public String getType() { + return type.getName(false); + } + + @Override + public boolean isConstructor() { + return false; + } + + @Override + public boolean isDeprecated() { + return false; + } + + @Override + public boolean isAbstract() { + return false; + } + + @Override + public boolean isFinal() { + return false; + } + + @Override + public boolean isStatic() { + return false; + } + }; + return fc; + } public static FieldCompletion createLengthCompletion( CompletionProvider provider, final Type type) { - FieldCompletion fc = new FieldCompletion(provider, type.toString()); + FieldCompletion fc = new FieldCompletion(provider, "length"); fc.data = new Data() { @Override diff --git a/src/main/java/org/fife/rsta/ac/java/FieldInfoData.java b/src/main/java/org/fife/rsta/ac/java/FieldInfoData.java index 38e5f92e..82bb1a45 100644 --- a/src/main/java/org/fife/rsta/ac/java/FieldInfoData.java +++ b/src/main/java/org/fife/rsta/ac/java/FieldInfoData.java @@ -10,6 +10,7 @@ */ package org.fife.rsta.ac.java; +import java.lang.ref.WeakReference; import java.util.Iterator; import org.fife.rsta.ac.java.MemberCompletion.Data; @@ -33,12 +34,12 @@ class FieldInfoData implements Data { private FieldInfo info; - private SourceCompletionProvider provider; + private WeakReference provider; public FieldInfoData(FieldInfo info, SourceCompletionProvider provider) { this.info = info; - this.provider = provider; + this.provider = new WeakReference(provider); } @@ -96,8 +97,9 @@ public String getSignature() { @Override public String getSummary() { - ClassFile cf = info.getClassFile();; - SourceLocation loc = provider.getSourceLocForClass(cf.getClassName(true)); + ClassFile cf = info.getClassFile(); + SourceLocation loc = null; + if (provider != null && provider.get() != null) loc = provider.get().getSourceLocForClass(cf.getClassName(true)); String summary = null; // First, try to parse the Javadoc for this method from the attached diff --git a/src/main/java/org/fife/rsta/ac/java/JarManager.java b/src/main/java/org/fife/rsta/ac/java/JarManager.java index 8ce0dd27..937419e0 100644 --- a/src/main/java/org/fife/rsta/ac/java/JarManager.java +++ b/src/main/java/org/fife/rsta/ac/java/JarManager.java @@ -96,11 +96,12 @@ public void addCompletions(CompletionProvider p, String text, // completions for classes not in their import statements. // Thanks to Guilherme Joao Frantz and Jonatas Schuler for the patch! else {//if (text.indexOf('.')==-1) { - String lowerCaseText = text.toLowerCase(); + // don't use lowercase here, since we would like to preserve the case for the textpartsmatch +// String lowerCaseText = text.toLowerCase(); for (int i=0; i classFiles = jar. - getClassesWithNamesStartingWith(lowerCaseText); + getClassesWithNamesStartingWith(text); if (classFiles!=null) { for (ClassFile cf : classFiles) { if (org.fife.rsta.ac.java.classreader.Util.isPublic(cf.getAccessFlags())) { diff --git a/src/main/java/org/fife/rsta/ac/java/JavaCellRenderer.java b/src/main/java/org/fife/rsta/ac/java/JavaCellRenderer.java index fc6dc3dc..2f2e2499 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaCellRenderer.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaCellRenderer.java @@ -15,6 +15,7 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; +import java.lang.ref.WeakReference; import java.util.Map; import javax.swing.DefaultListCellRenderer; import javax.swing.JList; @@ -39,7 +40,7 @@ public class JavaCellRenderer extends DefaultListCellRenderer { private JList list; private boolean selected; private boolean evenRow; - private JavaSourceCompletion jsc; + private WeakReference jsc; /** * The alternating background color, or null for none. @@ -92,9 +93,9 @@ public Component getListCellRendererComponent(JList list, Object value, this.selected = selected; if (value instanceof JavaSourceCompletion) { - jsc = (JavaSourceCompletion)value; + jsc = new WeakReference((JavaSourceCompletion)value); nonJavaCompletion = null; - setIcon(jsc.getIcon()); + setIcon(jsc.get().getIcon()); } else { jsc = null; @@ -157,11 +158,11 @@ protected void paintComponent(Graphics g) { int x = getX() + iconW + 2; g.setColor(selected ? list.getSelectionForeground() : list.getForeground()); - if (jsc!=null && !simpleText) { - jsc.rendererText(g, x, g.getFontMetrics().getHeight(), selected); + if (jsc!=null && jsc.get() != null && !simpleText) { + jsc.get().rendererText(g, x, g.getFontMetrics().getHeight(), selected); } else { - Completion c = jsc!=null ? jsc : nonJavaCompletion; + Completion c = jsc!=null && jsc.get() != null ? jsc.get() : nonJavaCompletion; if (c!=null) { g.drawString(c.toString(), x, g.getFontMetrics().getHeight()); } diff --git a/src/main/java/org/fife/rsta/ac/java/JavaCompletionProvider.java b/src/main/java/org/fife/rsta/ac/java/JavaCompletionProvider.java index b4646ec5..8f46bde9 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaCompletionProvider.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaCompletionProvider.java @@ -14,16 +14,13 @@ import java.io.File; import java.io.IOException; import java.util.List; +import javax.swing.*; import javax.swing.text.JTextComponent; import org.fife.rsta.ac.ShorthandCompletionCache; import org.fife.rsta.ac.java.buildpath.LibraryInfo; import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.ui.autocomplete.AbstractCompletionProvider; -import org.fife.ui.autocomplete.Completion; -import org.fife.ui.autocomplete.DefaultCompletionProvider; -import org.fife.ui.autocomplete.LanguageAwareCompletionProvider; -import org.fife.ui.autocomplete.ParameterizedCompletion; +import org.fife.ui.autocomplete.*; /** @@ -147,8 +144,8 @@ public List getJars() { @Override public List getParameterizedCompletions( JTextComponent tc) { - return null; - } + return sourceProvider.getParameterizedCompletions(cu, tc); + } /** diff --git a/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java b/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java index 96a111f2..24c63dcd 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java @@ -19,11 +19,9 @@ import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.KeyStroke; -import javax.swing.Timer; +import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.text.BadLocationException; @@ -32,8 +30,9 @@ import org.fife.rsta.ac.AbstractLanguageSupport; import org.fife.rsta.ac.GoToMemberAction; -import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; +import org.fife.rsta.ac.java.classreader.FieldInfo; +import org.fife.rsta.ac.java.classreader.MethodInfo; +import org.fife.rsta.ac.java.rjc.ast.*; import org.fife.rsta.ac.java.rjc.ast.Package; import org.fife.rsta.ac.java.tree.JavaOutlineTree; import org.fife.ui.autocomplete.AutoCompletion; @@ -162,10 +161,88 @@ public void install(RSyntaxTextArea textArea) { installKeyboardShortcuts(textArea); - textArea.setLinkGenerator(new JavaLinkGenerator(this)); - + textArea.setLinkGenerator(new JavaLinkGenerator(this, p)); + ((JavaLinkGenerator) textArea.getLinkGenerator()).setMemberClickedListener(new DefaultMemberClickedListener(textArea)); } + private static class DefaultMemberClickedListener implements MemberClickedListener + { + private RSyntaxTextArea textArea; + + public DefaultMemberClickedListener(RSyntaxTextArea textArea) { + this.textArea = textArea; + } + + @Override + public void openClass(String className) { + if (textArea.getLinkGenerator() instanceof JavaLinkGenerator) { + List externalListeners = ((JavaLinkGenerator) textArea.getLinkGenerator()).getExternalMemberClickedListeners(); + if (externalListeners != null && externalListeners.size() > 0) { + for (ExternalMemberClickedListener listener : externalListeners) { + listener.openClass(className); + } + } + } +// System.out.println("openClass: " + className); + } + + @Override + public void gotoMethodInClass(String className, MethodInfo methodInfo) { + if (textArea.getLinkGenerator() instanceof JavaLinkGenerator) { + List externalListeners = ((JavaLinkGenerator) textArea.getLinkGenerator()).getExternalMemberClickedListeners(); + if (externalListeners != null && externalListeners.size() > 0) { + for (ExternalMemberClickedListener listener : externalListeners) { + listener.gotoMethodInClass(className, methodInfo); + } + } + } +// System.out.println("gotoMethodInClass [" + className + "], method: " + methodInfo.getName()); + } + + @Override + public void gotoFieldInClass(String className, FieldInfo fieldInfo) { + if (textArea.getLinkGenerator() instanceof JavaLinkGenerator) { + List externalListeners = ((JavaLinkGenerator) textArea.getLinkGenerator()).getExternalMemberClickedListeners(); + if (externalListeners != null && externalListeners.size() > 0) { + for (ExternalMemberClickedListener listener : externalListeners) { + listener.gotoFieldInClass(className, fieldInfo); + } + } + } +// System.out.println("gotoFieldInClass [" + className + "], field: " + fieldInfo.getName()); + } + + @Override + public void gotoInnerClass(TypeDeclaration typeDeclaration) { +// System.out.println("gotoInnerClass: " + typeDeclaration.getName()); + textArea.setCaretPosition(typeDeclaration.getNameStartOffset()); + } + + @Override + public void gotoMethod(Method method) { +// System.out.println("gotoMethod: " + method.getName()); + textArea.setCaretPosition(method.getNameStartOffset()); + } + + @Override + public void gotoField(Field field) { +// System.out.println("gotoField: " + field.getName()); + textArea.setCaretPosition(field.getNameStartOffset()); + } + + @Override + public void gotoLocalVar(LocalVariable localVar) { +// System.out.println("gotoLocalVar: " + localVar.getName()); + textArea.setCaretPosition(localVar.getNameStartOffset()); + } + + @Override + public void gotoMethodParameter(FormalParameter parameter) { +// System.out.println("gotoMethodParameter: " + parameter.getName()); + textArea.setCaretPosition(parameter.getNameStartOffset()); + } + } + /** * Installs extra keyboard shortcuts supported by this language support. @@ -357,7 +434,7 @@ protected String getReplacementText(Completion c, Document doc, * Thanks to Guilherme Joao Frantz and Jonatas Schuler for helping * with the patch! * - * @param c The completion being inserted. + * @param cc The completion being inserted. * @return Whether an import was added. */ private ImportToAddInfo getShouldAddImport(ClassCompletion cc) { @@ -383,7 +460,14 @@ private ImportToAddInfo getShouldAddImport(ClassCompletion cc) { } String className = cc.getClassName(false); - String fqClassName = cc.getClassName(true); + String fqClassName = cc.getClassName(true); + // if the unqualified class name already contains a dot, this coult be an inner class declaration. + // check if enclosing class is imported; + if (className.contains(".")) { + // split at the first ., since it will be our outer class name + className = className.substring(0, className.indexOf(".")); + fqClassName = fqClassName.substring(0, fqClassName.indexOf(className) + className.length()); + } // If the completion is in the same package as the source we're // editing (or both are in the default package), bail. diff --git a/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java b/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java index 53087cea..f8064a68 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java @@ -10,26 +10,16 @@ */ package org.fife.rsta.ac.java; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import javax.swing.event.HyperlinkEvent; import javax.swing.text.BadLocationException; -import org.fife.rsta.ac.java.rjc.ast.CodeBlock; import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.FormalParameter; -import org.fife.rsta.ac.java.rjc.ast.LocalVariable; -import org.fife.rsta.ac.java.rjc.ast.Member; import org.fife.rsta.ac.java.rjc.ast.Method; import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; -import org.fife.ui.rsyntaxtextarea.LinkGenerator; -import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; -import org.fife.ui.rsyntaxtextarea.SelectRegionLinkGeneratorResult; -import org.fife.ui.rsyntaxtextarea.Token; -import org.fife.ui.rsyntaxtextarea.TokenImpl; +import org.fife.ui.rsyntaxtextarea.*; + +import java.util.ArrayList; +import java.util.List; /** @@ -44,17 +34,52 @@ * @version 1.0 */ // TODO: Anonymous inner classes probably aren't handled well. -class JavaLinkGenerator implements LinkGenerator { +public class JavaLinkGenerator implements LinkGenerator { private JavaLanguageSupport jls; + private JavaCompletionProvider javaCompletionProvider; + private SourceCompletionProvider sourceCompletionProvider; + private MemberClickedListener memberClickedListener; + private List externalMemberClickedListeners = new ArrayList(); - - JavaLinkGenerator(JavaLanguageSupport jls) { + JavaLinkGenerator(JavaLanguageSupport jls, JavaCompletionProvider p) { this.jls = jls; + javaCompletionProvider = p; + sourceCompletionProvider = (SourceCompletionProvider)javaCompletionProvider. + getDefaultCompletionProvider(); } - - /** + JavaLinkGenerator(JavaLanguageSupport jls, JavaCompletionProvider p, MemberClickedListener memberClickedListener) { + this.jls = jls; + javaCompletionProvider = p; + sourceCompletionProvider = (SourceCompletionProvider)javaCompletionProvider. + getDefaultCompletionProvider(); + this.memberClickedListener = memberClickedListener; + } + + public void setMemberClickedListener(MemberClickedListener memberClickedListener) { + this.memberClickedListener = memberClickedListener; + } + + public MemberClickedListener getMemberClickedListener() { + return memberClickedListener; + } + + public List getExternalMemberClickedListeners() { + return externalMemberClickedListeners; + } + + public void addExternalMemberClickedListener(ExternalMemberClickedListener externalMemberClickedListener) { + if (!externalMemberClickedListeners.contains(externalMemberClickedListener)) { + externalMemberClickedListeners.add(externalMemberClickedListener); + } + } + + public void removeExternalMemberClickedListener(ExternalMemberClickedListener externalMemberClickedListener) { + externalMemberClickedListeners.remove(externalMemberClickedListener); + } + + /** * Checks if the token at the specified offset is possibly a "click-able" * region. * @@ -85,29 +110,79 @@ private IsLinkableCheckResult checkForLinkableToken( Token token = new TokenImpl(t); boolean isMethod = false; - if (prev==null) { - prev = RSyntaxUtilities.getPreviousImportantToken( - doc, line-1); - } - if (prev!=null && prev.isSingleChar('.')) { - // Not a field or method defined in this class. - break; - } - - Token next = RSyntaxUtilities.getNextImportantToken( - t.getNextToken(), textArea, line); - if (next!=null && next.isSingleChar(Token.SEPARATOR, '(')) { - isMethod = true; - } - - result = new IsLinkableCheckResult(token, isMethod); - break; + Token firstE = token; + + int start=firstE.getOffset(); + // get the whole line only if the prev token is dot + if (prev != null && prev.isSingleChar('.')) + { + String alreadyEnteredTextS2 = SourceCompletionProvider.getAlreadyEnteredTextS2(textArea, firstE.getOffset()); + start = start - alreadyEnteredTextS2.length(); + } + + int end = token.getEndOffset(); + + // if next token is ( we need to find the corresponding closing ) token, and set the end to the closing ) + if (t.getNextToken() != null && t.getNextToken().getType() != TokenTypes.NULL && t.getNextToken().isSingleChar('(')) + { + int bcounter = 1; + // start from the token after the ( + Token tmp = t.getNextToken().getNextToken(); + while (tmp.getNextToken() != null && bcounter > 0) { + if (tmp.isSingleChar('(')) bcounter++; + if (tmp.isSingleChar(')')) bcounter--; + if (bcounter > 0) tmp = tmp.getNextToken(); + } + + // we managed to find the closing ) w + if (bcounter == 0 && tmp != null) { + end = tmp.getEndOffset(); + } + } + +// Token last= token; + +// int maxLength = textArea.getText().length(); +// String text2 = textArea.getText(end, Math.min(maxLength, 10)); +// if(text2.trim().startsWith("(")) { +// int closeBracket = text2.indexOf(')'); +// end+=closeBracket+1; +// } + + IsLinkableCheckResult aa = new IsLinkableCheckResult(token, isMethod); + aa.start = start; // first.getOffset(); + aa.end = end; + int length = aa.end - aa.start; + if(length<1) { + } else { + String text = textArea.getText(aa.start, length); + aa.text = text; + return aa; + } + +// if (prev==null) { +// prev = RSyntaxUtilities.getPreviousImportantToken( +// doc, line-1); +// } +// if (prev!=null && prev.isSingleChar('.')) { +// // Not a field or method defined in this class. +// break; +// } +// +// Token next = RSyntaxUtilities.getNextImportantToken( +// t.getNextToken(), textArea, line); +// if (next!=null && next.isSingleChar(Token.SEPARATOR, '(')) { +// isMethod = true; +// } +// +// result = new IsLinkableCheckResult(token, isMethod); +// break; } - else if (!t.isCommentOrWhitespace()) { - prev = t; - } +// else if (!t.isCommentOrWhitespace()) { + prev = t; +// } } @@ -121,109 +196,134 @@ else if (!t.isCommentOrWhitespace()) { } + long lastAccess = -1; /** * {@inheritDoc} */ @Override - public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, - int offs) { + public LinkGeneratorResult isLinkAtOffset(final RSyntaxTextArea textArea, + final int offs) { int start = -1; int end = -1; - IsLinkableCheckResult result = checkForLinkableToken(textArea, offs); + final IsLinkableCheckResult result = checkForLinkableToken(textArea, offs); if (result!=null) { JavaParser parser = jls.getParser(textArea); - CompilationUnit cu = parser.getCompilationUnit(); + final CompilationUnit cu = parser.getCompilationUnit(); Token t = result.token; boolean method = result.method; if (cu!=null) { - TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(offs); - boolean staticFieldsOnly = false; - boolean deepestTypeDec = true; - boolean deepestContainingMemberStatic = false; - while (td!=null && start==-1) { - - // First, check for a local variable in methods/static blocks - if (!method && deepestTypeDec) { - - Iterator i = td.getMemberIterator(); - while (i.hasNext()) { - - Method m = null; // Nasty! Clean this code up - Member member = i.next(); - CodeBlock block = null; - - // Check if a method or static block contains offs - if (member instanceof Method) { - m = (Method)member; - if (m.getBodyContainsOffset(offs) && m.getBody()!=null) { - deepestContainingMemberStatic = m.isStatic(); - block = m.getBody().getDeepestCodeBlockContaining(offs); - } - } - else if (member instanceof CodeBlock) { - block = (CodeBlock)member; - deepestContainingMemberStatic = block.isStatic(); - block = block.getDeepestCodeBlockContaining(offs); - } - - // If so, scan its locals - if (block!=null) { - String varName = t.getLexeme(); - // Local variables first, in reverse order - List locals = block.getLocalVarsBefore(offs); - Collections.reverse(locals); - for (LocalVariable local : locals) { - if (varName.equals(local.getName())) { - start = local.getNameStartOffset(); - end = local.getNameEndOffset(); - } - } - // Then arguments, if any. - if (start==-1 && m!=null) { - for (int j=0; j i = method ? - td.getMethodIterator() : td.getFieldIterator(); - while (i.hasNext()) { - Member member = i.next(); - if (((!deepestContainingMemberStatic && !staticFieldsOnly) || member.isStatic()) && - varName.equals(member.getName())) { - start = member.getNameStartOffset(); - end = member.getNameEndOffset(); - break; - } - } - } - - // If still no match found, check parent type - if (start==-1) { - staticFieldsOnly |= td.isStatic(); - //td = td.isStatic() ? null : td.getParentType(); - td = td.getParentType(); - // Don't check for local vars in parent type methods. - deepestTypeDec = false; - } + final TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(offs); +// boolean staticFieldsOnly = false; +// boolean deepestTypeDec = true; +// boolean deepestContainingMemberStatic = false; + if(td != null && start == -1) { + final Method findCurrentMethod = SourceCompletionProvider.findCurrentMethod(td, offs); +// if (findCurrentMethod != null) { + if(System.currentTimeMillis()-lastAccess < 2000) { + return null; + } + return new LinkGeneratorResult() { + + @Override + public int getSourceOffset() { +// log.info(1); + return 0; + } + + @Override + public HyperlinkEvent execute() { + if (memberClickedListener != null) { + String text2 = result.text.replace("@", ""); + sourceCompletionProvider.open(cu, result.text, td, findCurrentMethod, text2, offs, offs - result.start, memberClickedListener); + } +// log.info(2); + return null; + } + }; +// } else { +// } +// // First, check for a local variable in methods/static blocks +// if (!method && deepestTypeDec) { +// +// Iterator i = td.getMemberIterator(); +// while (i.hasNext()) { +// +// Method m = null; // Nasty! Clean this code up +// Member member = i.next(); +// CodeBlock block = null; +// +// // Check if a method or static block contains offs +// if (member instanceof Method) { +// m = (Method)member; +// if (m.getBodyContainsOffset(offs) && m.getBody()!=null) { +// deepestContainingMemberStatic = m.isStatic(); +// block = m.getBody().getDeepestCodeBlockContaining(offs); +// } +// } +// else if (member instanceof CodeBlock) { +// block = (CodeBlock)member; +// deepestContainingMemberStatic = block.isStatic(); +// block = block.getDeepestCodeBlockContaining(offs); +// } +// +// // If so, scan its locals +// if (block!=null) { +// String varName = t.getLexeme(); +// // Local variables first, in reverse order +// List locals = block.getLocalVarsBefore(offs); +// Collections.reverse(locals); +// for (LocalVariable local : locals) { +// if (varName.equals(local.getName())) { +// start = local.getNameStartOffset(); +// end = local.getNameEndOffset(); +// } +// } +// // Then arguments, if any. +// if (start==-1 && m!=null) { +// for (int j=0; j i = method ? +// td.getMethodIterator() : td.getFieldIterator(); +// while (i.hasNext()) { +// Member member = i.next(); +// if (((!deepestContainingMemberStatic && !staticFieldsOnly) || member.isStatic()) && +// varName.equals(member.getName())) { +// start = member.getNameStartOffset(); +// end = member.getNameEndOffset(); +// break; +// } +// } +// } +// +// // If still no match found, check parent type +// if (start==-1) { +// staticFieldsOnly |= td.isStatic(); +// //td = td.isStatic() ? null : td.getParentType(); +// td = td.getParentType(); +// // Don't check for local vars in parent type methods. +// deepestTypeDec = false; +// } } @@ -247,11 +347,15 @@ else if (member instanceof CodeBlock) { */ private static class IsLinkableCheckResult { + public String text; /** * The token under the mouse position. */ private Token token; + private int start; + private int end; + /** * Whether the token is a method invocation (as opposed to a local * variable or field). diff --git a/src/main/java/org/fife/rsta/ac/java/JavaParser.java b/src/main/java/org/fife/rsta/ac/java/JavaParser.java index 4e11b1f9..f3673c35 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaParser.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaParser.java @@ -117,6 +117,9 @@ public CompilationUnit getCompilationUnit() { public int getOffset(RSyntaxDocument doc, ParserNotice notice) { Element root = doc.getDefaultRootElement(); Element elem = root.getElement(notice.getLine()); + if (elem == null) { + return -1; + } int offs = elem.getStartOffset() + notice.getColumn(); return offs>=elem.getEndOffset() ? -1 : offs; } diff --git a/src/main/java/org/fife/rsta/ac/java/MemberClickedListener.java b/src/main/java/org/fife/rsta/ac/java/MemberClickedListener.java new file mode 100644 index 00000000..5eae6d41 --- /dev/null +++ b/src/main/java/org/fife/rsta/ac/java/MemberClickedListener.java @@ -0,0 +1,41 @@ +package org.fife.rsta.ac.java; + +import org.fife.rsta.ac.java.classreader.FieldInfo; +import org.fife.rsta.ac.java.classreader.MethodInfo; +import org.fife.rsta.ac.java.rjc.ast.*; + +/** + * @since 2017.03.06. + */ +public interface MemberClickedListener extends ExternalMemberClickedListener +{ + /** + * Indicates user clicked on an inner class + * @param typeDeclaration the typeDeclaration structure holding information about the inner class + */ + void gotoInnerClass(TypeDeclaration typeDeclaration); + + /** + * Indicates user clicked a method within current document + * @param method the method structure holding information about the clicked method + */ + void gotoMethod(Method method); + + /** + * Indicates user clicked a field within current document + * @param field the field structure holding information about the clicked field + */ + void gotoField(Field field); + + /** + * Indicates user clicked a local variable within current document + * @param localVar the localvariable structure holding information about the clicked local variable + */ + void gotoLocalVar(LocalVariable localVar); + + /** + * Indicates user clicked a variable which is a parameter in the current method + * @param parameter the formalparameter structure holding information about the clicked method parameter + */ + void gotoMethodParameter(FormalParameter parameter); +} diff --git a/src/main/java/org/fife/rsta/ac/java/MethodCompletion.java b/src/main/java/org/fife/rsta/ac/java/MethodCompletion.java index 14f24911..4d064f83 100644 --- a/src/main/java/org/fife/rsta/ac/java/MethodCompletion.java +++ b/src/main/java/org/fife/rsta/ac/java/MethodCompletion.java @@ -63,6 +63,10 @@ class MethodCompletion extends FunctionCompletion implements MemberCompletion { private static final int NON_CONSTRUCTOR_RELEVANCE = 2; + public MethodCompletion(CompletionProvider provider, String name, Type type) { + super(provider, name, type == null ? "void" : type.toString()); + } + /** * Creates a completion for a method discovered when parsing a Java * source file. @@ -332,5 +336,71 @@ public String toString() { return getSignature(); } - + /** + * Returns a MethodCompletion instance using super keyword as method name and using parameters from provided + * method + * @param provider + * @param m + * @return + */ + public static MethodCompletion createSuperConstructorCompletion(CompletionProvider provider, Method m) { + int count = m.getParameterCount(); + List params = new ArrayList(count); + for (int i=0; i params = new ArrayList(paramTypes.length); + for (int i=0; i()); + mc.setRelevanceAppropriately(); + + return mc; + } } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/java/MethodData.java b/src/main/java/org/fife/rsta/ac/java/MethodData.java index 3bbe9aa8..3e627c93 100644 --- a/src/main/java/org/fife/rsta/ac/java/MethodData.java +++ b/src/main/java/org/fife/rsta/ac/java/MethodData.java @@ -57,7 +57,10 @@ public String getIcon() { String key = null; Modifiers mod = method.getModifiers(); - if (mod==null) { + if (method.getType() == null) { + key = IconFactory.CONSTRUCTOR_ICON; + } + else if (mod==null) { key = IconFactory.METHOD_DEFAULT_ICON; } else if (mod.isPrivate()) { diff --git a/src/main/java/org/fife/rsta/ac/java/MethodInfoData.java b/src/main/java/org/fife/rsta/ac/java/MethodInfoData.java index 697be3b0..4c47ab45 100644 --- a/src/main/java/org/fife/rsta/ac/java/MethodInfoData.java +++ b/src/main/java/org/fife/rsta/ac/java/MethodInfoData.java @@ -10,6 +10,7 @@ */ package org.fife.rsta.ac.java; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -38,7 +39,7 @@ class MethodInfoData implements Data { /** * The parent completion provider. */ - private SourceCompletionProvider provider; + private WeakReference provider; /** * The actual metadata. @@ -59,7 +60,7 @@ class MethodInfoData implements Data { */ public MethodInfoData(MethodInfo info, SourceCompletionProvider provider) { this.info = info; - this.provider = provider; + this.provider = new WeakReference(provider); } @@ -81,7 +82,10 @@ public String getIcon() { String key = null; int flags = info.getAccessFlags(); - if (Util.isDefault(flags)) { + if (info.getReturnTypeFull() == null) { + key = IconFactory.CONSTRUCTOR_ICON; + } + else if (Util.isDefault(flags)) { key = IconFactory.METHOD_DEFAULT_ICON; } else if (Util.isPrivate(flags)) { @@ -299,7 +303,8 @@ public String getSignature() { public String getSummary() { ClassFile cf = info.getClassFile(); - SourceLocation loc = provider.getSourceLocForClass(cf.getClassName(true)); + SourceLocation loc = null; + if (provider != null && provider.get() != null) loc = provider.get().getSourceLocForClass(cf.getClassName(true)); String summary = null; // First, try to parse the Javadoc for this method from the attached diff --git a/src/main/java/org/fife/rsta/ac/java/PackageMapNode.java b/src/main/java/org/fife/rsta/ac/java/PackageMapNode.java index 4ea5bff3..b4e5424d 100644 --- a/src/main/java/org/fife/rsta/ac/java/PackageMapNode.java +++ b/src/main/java/org/fife/rsta/ac/java/PackageMapNode.java @@ -341,6 +341,7 @@ void getClassesWithNamesStartingWith(LibraryInfo info, String prefix, String currentPkg, List addTo) { final int prefixLen = prefix.length(); + List prefixParts = org.fife.ui.autocomplete.Util.getTextParts(prefix); for (Map.Entry children : subpackages.entrySet()) { String key = children.getKey(); @@ -354,7 +355,8 @@ void getClassesWithNamesStartingWith(LibraryInfo info, String prefix, // necessary (i.e. if the class name does match what they've // typed). String className = cfEntry.getKey(); - if (className.regionMatches(true, 0, prefix, 0, prefixLen)) { + // extend the match with our textPartsMatch, creating a match for example JTP with JTabbedPane + if (className.regionMatches(true, 0, prefix.toLowerCase(), 0, prefixLen) || org.fife.ui.autocomplete.Util.matchTextParts(prefixParts, org.fife.ui.autocomplete.Util.getTextParts(className))) { ClassFile cf = cfEntry.getValue(); if (cf==null) { String fqClassName = currentPkg + className + ".class"; diff --git a/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java b/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java index b913f95b..df4fd753 100644 --- a/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java +++ b/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java @@ -14,42 +14,30 @@ import java.awt.Point; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.io.StringReader; +import java.util.*; + import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; import javax.swing.text.JTextComponent; +import javax.swing.text.Segment; import org.fife.rsta.ac.ShorthandCompletionCache; import org.fife.rsta.ac.java.buildpath.LibraryInfo; import org.fife.rsta.ac.java.buildpath.SourceLocation; -import org.fife.rsta.ac.java.classreader.ClassFile; -import org.fife.rsta.ac.java.classreader.FieldInfo; -import org.fife.rsta.ac.java.classreader.MemberInfo; -import org.fife.rsta.ac.java.classreader.MethodInfo; -import org.fife.rsta.ac.java.rjc.ast.CodeBlock; -import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.Field; -import org.fife.rsta.ac.java.rjc.ast.FormalParameter; -import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; -import org.fife.rsta.ac.java.rjc.ast.LocalVariable; -import org.fife.rsta.ac.java.rjc.ast.Member; -import org.fife.rsta.ac.java.rjc.ast.Method; -import org.fife.rsta.ac.java.rjc.ast.NormalClassDeclaration; -import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; +import org.fife.rsta.ac.java.classreader.*; +import org.fife.rsta.ac.java.rjc.ast.*; import org.fife.rsta.ac.java.rjc.lang.Type; import org.fife.rsta.ac.java.rjc.lang.TypeArgument; import org.fife.rsta.ac.java.rjc.lang.TypeParameter; +import org.fife.rsta.ac.java.rjc.lexer.Scanner; +import org.fife.rsta.ac.java.rjc.lexer.TokenTypes; +import org.fife.ui.autocomplete.AbstractCompletion; import org.fife.ui.autocomplete.Completion; import org.fife.ui.autocomplete.DefaultCompletionProvider; -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.autocomplete.ParameterizedCompletion; +import org.fife.ui.rsyntaxtextarea.*; import org.fife.ui.rsyntaxtextarea.Token; @@ -70,6 +58,8 @@ */ class SourceCompletionProvider extends DefaultCompletionProvider { + public static boolean loadPrivateMemberAlways = true; + /** * The parent completion provider. */ @@ -82,6 +72,9 @@ class SourceCompletionProvider extends DefaultCompletionProvider { private static final String JAVA_LANG_PACKAGE = "java.lang.*"; private static final String THIS = "this"; + private static final String SUPER = "super"; + + private boolean loadConstructors = false; //Shorthand completions (templates and comments) private ShorthandCompletionCache shorthandCache; @@ -156,7 +149,7 @@ private void addCompletionsForStaticMembers(Set set, */ private void addCompletionsForExtendedClass(Set set, CompilationUnit cu, ClassFile cf, String pkg, - Map typeParamMap) { + Map typeParamMap, boolean staticOnly) { // Reset this class's type-arguments-to-type-parameters map, so that // when methods and fields need to know type arguments, they can query @@ -169,7 +162,7 @@ private void addCompletionsForExtendedClass(Set set, for (int i=0; i set, int fieldCount = cf.getFieldCount(); for (int i=0; i packageFiles = jarManager.getClassesInPackage(cf.getPackageName(), true); + for (int i = 0;packageFiles != null && i < packageFiles.size();i++) { + ClassFile pf = packageFiles.get(i); + if (pf.getClassName(true).startsWith(cf.getClassName(true) + ".") && (pf.getAccessFlags() & AccessFlags.ACC_PUBLIC) > 0) { + set.add(new ClassCompletion(this, pf, pf.getClassName(false).substring(pf.getClassName(false).lastIndexOf(".") + 1))); + } + } + // Add completions for any non-overridden super-class methods. ClassFile superClass = getClassFileFor(cu, cf.getSuperClassName(true)); if (superClass!=null) { - addCompletionsForExtendedClass(set, cu, superClass, pkg, typeParamMap); + // typeParamMap should be recreated since there can be marker change (eg. from T to E or similar) + Map superTypeParamMap = translateBetweenParamMaps(superClass, typeParamMap); +// if (superClass.getParamTypes() != null && typeParamMap != null) { +// List superParamTypes = superClass.getParamTypes(); +// List paramTypes = new ArrayList(typeParamMap.values()); +// for (int i = 0;i < superParamTypes.size();i++) { +// String typ = superParamTypes.get(i); +// // if we have a matching type, we use it +// if (typeParamMap.containsKey(typ)) superTypeParamMap.put(typ, typeParamMap.get(typ)); +// // else check if we have a matching pair in the original type map, if yes, use it (may be the +// // type argument letter has changed +// else if (i < paramTypes.size()) superTypeParamMap.put(typ, paramTypes.get(i)); +// // otherwise use what provided in the superclass +// else superTypeParamMap.put(typ, superClass.getTypeArgument(typ)); +// } +// } + addCompletionsForExtendedClass(set, cu, superClass, pkg, superTypeParamMap, staticOnly); } // Add completions for any interface methods, in case this class is @@ -196,13 +214,83 @@ private void addCompletionsForExtendedClass(Set set, for (int i=0; i set, CompilationUnit cu, + TypeDeclaration td, Map typeParamMap, boolean staticOnly) { + + // Check us first, so if we override anything, we get the "newest" + // version. + int memberCount = td.getMemberCount(); + for (int i=0; i superTypeParamMap = new HashMap(); + if (superClass.getParamTypes() != null) { + List superParamTypes = superClass.getParamTypes(); + List paramTypes = new ArrayList(typeParamMap.values()); + for (int i = 0;i < superParamTypes.size();i++) { + String typ = superParamTypes.get(i); + // if we have a matching type, we use it + if (typeParamMap.containsKey(typ)) superTypeParamMap.put(typ, typeParamMap.get(typ)); + // else check if we have a matching pair in the original type map, if yes, use it (may be the + // type argument letter has changed + else if (i < paramTypes.size()) superTypeParamMap.put(typ, paramTypes.get(i)); + // otherwise use what provided in the superclass + else superTypeParamMap.put(typ, superClass.getTypeArgument(typ)); + } + } + addCompletionsForExtendedClass(set, cu, superClass, cu.getPackageName(), superTypeParamMap, staticOnly); + } + Iterator intfIterator = ncTd.getImplementedIterator(); + while (intfIterator.hasNext()) { + String intf = intfIterator.next().getName(true, false); + ClassFile cf = getClassFileFor(cu, intf); + if (cf != null) { + // create type param map + addCompletionsForExtendedClass(set, cu, cf, cu.getPackageName(), typeParamMap, staticOnly); + } + } + + // add completions for public & public static inner classes + if (td.getChildTypeCount() > 0) { + for (int i = 0;i < td.getChildTypeCount();i++) { + TypeDeclaration innerTd = td.getChildType(i); + if (innerTd.getModifiers().isPublic() || (innerTd.isStatic() && innerTd.getModifiers().isPublic())) { + set.add(new TypeDeclarationCompletion(this, innerTd)); + } + } + } + } + +// // Add completions for any interface methods, in case this class is +// // abstract and hasn't implemented some of them yet. +// // TODO: Do this only if "top-level" class is declared abstract +// for (int i=0; i typeParamMap = createTypeParamMap(type, cf); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); + addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap, false); } + else { + // add completions for inner classes + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + TypeDeclaration td = cu.getTypeDeclaration(i); + + for (int j = 0;j < td.getChildTypeCount();j++) { + TypeDeclaration childTd = td.getChildType(j); + if (var.getType().getName(false).equals(childTd.getName())) { + addCompletionsForInnerClass(retVal, cu, childTd, createTypeParamMap(var.getType(), childTd), false); + } + } + } + } } } @@ -307,7 +408,7 @@ else if (imported.endsWith("." + className)) { } // Finally, try java.lang - if (superClass==null) { + if (superClass==null && !className.equals("void")) { String temp = "java.lang." + className; superClass = jarManager.getClassEntry(temp); } @@ -431,12 +532,11 @@ private boolean checkStringLiteralMember(JTextComponent comp, if (prevToken!=null && prevToken.getType()==Token.LITERAL_STRING_DOUBLE_QUOTE) { ClassFile cf = getClassFileFor(cu, "java.lang.String"); - addCompletionsForExtendedClass(set, cu, cf, - cu.getPackageName(), null); + addCompletionsForExtendedClass(set, cu, cf, cu.getPackageName(), null, false); stringLiteralMember = true; } else { - System.out.println(prevToken); + // System.out.println(prevToken); } } } catch (BadLocationException ble) { // Never happens @@ -464,6 +564,28 @@ public void clearJars() { clear(); } + /** + * Creates and returns a mapping of type parameters to type arguments in the local CU + * @param td + * @return + */ + private Map createTypeParamMap(Type type, TypeDeclaration td) { + Map typeParamMap = null; + List typeArgs = type.getTypeArguments(type.getIdentifierCount()-1); + if (typeArgs != null && td instanceof NormalClassDeclaration) { + List typeParameters = ((NormalClassDeclaration) td).getTypeParameters(); + if (typeParameters != null) { + typeParamMap = new LinkedHashMap(); + int min = Math.min(typeParameters.size(), + typeArgs.size()); + for (int i=0; i createTypeParamMap(Type type, ClassFile cf) { Map typeParamMap = null; List typeArgs = type.getTypeArguments(type.getIdentifierCount()-1); if (typeArgs!=null) { - typeParamMap = new HashMap(); + typeParamMap = new LinkedHashMap(); List paramTypes = cf.getParamTypes(); // Should be the same size! Otherwise, the source code has // too many/too few type arguments listed for this type. @@ -511,6 +633,8 @@ public List getCompletionsAt(JTextComponent tc, Point p) { @Override protected List getCompletionsImpl(JTextComponent comp) { + loadConstructors = false; + comp.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { @@ -523,71 +647,804 @@ protected List getCompletionsImpl(JTextComponent comp) { } Set set = new TreeSet(); + Set returnSet = new TreeSet(); // Cut down the list to just those matching what we've typed. // Note: getAlreadyEnteredText() never returns null String text = getAlreadyEnteredText(comp); + boolean hasSpecialCompletions = false; + + // check if we have a "new" keyword before the entered text + // if yes, find the type we try to create, and load only the constructors of that type, nothing else + boolean hasNewKw = hasNewKeyword(comp); + if (hasNewKw) { + // get the type of: variable, if we have a = before the new + String variableBeforeNew = getVariableBeforeNew(comp); + if (variableBeforeNew != null) { + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(comp.getCaretPosition()); + if (td != null) { + Type type = resolveType2(cu, variableBeforeNew, td, findCurrentMethod(td, comp.getCaretPosition()), variableBeforeNew, comp.getCaretPosition()); + if (type != null) { + loadConstructorsForType(cu, returnSet, type, false); + hasSpecialCompletions = true; + } + } + } + // get the type of: method return parameter if we encounter keyword return before the new + else if (hasReturnKeywordBeforeNew(comp)) { + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(comp.getCaretPosition()); + if (td != null) { + Method c = findCurrentMethod(td, comp.getCaretPosition()); + if (c != null) { + loadConstructorsForType(cu, returnSet, c.getType(), false); + hasSpecialCompletions = true; + } + } + } + // here we could check if the new is in a method parameter and try to get the parameter type + else if (newIsInMethodParameter(cu, returnSet, comp)) { + hasSpecialCompletions = true; + } + else + // if just a new keyword is present (without = or return), + // load constructors for completion classes + loadConstructors = true; + } + + // if there was no new keyword, check if we are in a method parameter, and if so, try to load completions + // for given parameter type in the method call + if (!hasNewKw) { + // if this method returns true, we do not load any other completions, since this succeeded to load + // completion proposals for the given method parameter + if (isInMethodParameter(cu, returnSet, comp)) { + hasSpecialCompletions = true; + } + // if there was no new keyword and was not in a method parameter, check if it is a value addition, like + // "List x = ", and load completions for given type from local vars, methods, fields, etc. + else if (isAssigningVariableValue(cu, returnSet, comp)) { + hasSpecialCompletions = true; + } + } + + // Special case - end of a String literal + boolean stringLiteralMember = checkStringLiteralMember(comp, text, cu, + set); + + // Not after a String literal - regular code completion + if (!stringLiteralMember) + { + + // Don't add shorthand completions if they're typing something + // qualified + if (text.indexOf('.') == -1) + { + addShorthandCompletions(set); + } + + loadImportCompletions(set, text, cu); + + // Add completions for fully-qualified stuff (e.g. "com.sun.jav") + //long startTime = System.currentTimeMillis(); + jarManager.addCompletions(this, text, set); + //long time = System.currentTimeMillis() - startTime; + //System.out.println("jar completions loaded in: " + time); + + // Loop through all types declared in this source, and provide + // completions depending on in what type/method/etc. the caret's in. + loadCompletionsForCaretPosition(cu, comp, text, set); + } + + // if we loaded some special completions above for new keyword or method parameter or variable assignment, + // add them to the from of the list, so they will appear at the top by setting the relevace some higher than + // the default 0/3, then add the completion set to this set, so it will filter out double elements + if (hasSpecialCompletions) { + for (Completion completion : returnSet) { + if (completion instanceof AbstractCompletion) { + ((AbstractCompletion) completion).setRelevance(5); + } + } + returnSet.addAll(set); + set = returnSet; + } + + // Do a final sort of all of our completions and we're good to go! + List sortedCompletions = new ArrayList(set); + Collections.sort(sortedCompletions); + + completions.addAll(set); - // Special case - end of a String literal - boolean stringLiteralMember = checkStringLiteralMember(comp, text, cu, - set); - - // Not after a String literal - regular code completion - if (!stringLiteralMember) { - - // Don't add shorthand completions if they're typing something - // qualified - if (text.indexOf('.')==-1) { - addShorthandCompletions(set); - } - - loadImportCompletions(set, text, cu); - - // Add completions for fully-qualified stuff (e.g. "com.sun.jav") - //long startTime = System.currentTimeMillis(); - jarManager.addCompletions(this, text, set); - //long time = System.currentTimeMillis() - startTime; - //System.out.println("jar completions loaded in: " + time); + // Only match based on stuff after the final '.', since that's what is + // displayed for all of our completions. + text = text.substring(text.lastIndexOf('.')+1); - // Loop through all types declared in this source, and provide - // completions depending on in what type/method/etc. the caret's in. - loadCompletionsForCaretPosition(cu, comp, text, set); + // do enhanced matching + return getCompletionSublist(completions, text); + +// @SuppressWarnings("unchecked") +// int start = Collections.binarySearch(completions, text, comparator); +// if (start<0) { +// start = -(start+1); +// } +// else { +// // There might be multiple entries with the same input text. +// while (start>0 && +// comparator.compare(completions.get(start-1), text)==0) { +// start--; +// } +// } +// +// @SuppressWarnings("unchecked") +// int end = Collections.binarySearch(completions, text+'{', comparator); +// end = -(end+1); +// +// return completions.subList(start, end); + } finally { + comp.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); } - // Do a final sort of all of our completions and we're good to go! - completions = new ArrayList(set); - Collections.sort(completions); + } - // Only match based on stuff after the final '.', since that's what is - // displayed for all of our completions. - text = text.substring(text.lastIndexOf('.')+1); + /** + * Checks if the cursor is after a variable value assignment (eg. List x = ) and if so, it tries to load completions + * for the given variable type. + * + * @param cu + * @param set + * @param comp + * @return + */ + private boolean isAssigningVariableValue(CompilationUnit cu, Set set, JTextComponent comp) { + org.fife.rsta.ac.java.rjc.lexer.Token token; + List tokens = getTokenListForLine(comp); + if (tokens == null) return false; + + if (tokens.size() > 0) { + // check backward the tokens check if we encounter = as the last token. + // the last token should be = and before the = there should be no operator any more. + int i = tokens.size() - 1; + token = tokens.get(i); + String varName; + if (token.getType() == TokenTypes.OPERATOR_EQUALS && i > 0 && (tokens.get(i - 1).getType() & TokenTypes.OPERATOR) == 0) { + varName = tokens.get(i - 1).getLexeme(); + } + else { + return false; + } + + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(comp.getCaretPosition()); + if (varName != null && td != null) { + Type type = resolveType2(cu, varName, td, findCurrentMethod(td, comp.getCaretPosition()), varName, comp.getCaretPosition()); + if (type != null) { + loadCompletionsForType(cu, set, type, comp); + // check if the local variable is in the completions list (normally should be), and remove it + for (Completion c : set) { + if (varName.equals(c.getReplacementText())) { + set.remove(c); + break; + } + } + return true; + } + + } + } + return false; + } + + /** + * This method will return possible types in a method call for a given parameter position. Since multiple similar + * signature can exist for given method name, it loads them all. It does not do a proper type matching, just checks + * the parameter count. It will take methods with more parameters than the current index also in account. + * @param cu + * @param tokens + * @param comp + * @return + */ + private List getMethodCallParameterTypes(CompilationUnit cu, List tokens, JTextComponent comp) { + org.fife.rsta.ac.java.rjc.lexer.Token token; + + List result = new ArrayList(); + + int paramCounter = 0; + int parenCounter = 0; + int newIndex = -1; + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + // increate the paramCounter only, if we are in the main method call, and not in some submethod call. + // so if parentCounter equals 0, means we are in the main method, if it is greater than 0, we encountered some + // other method call inside the method call + if (token.getType() == TokenTypes.SEPARATOR_COMMA && parenCounter == 0) paramCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_RPAREN) parenCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter > 0) parenCounter--; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter == 0) { + newIndex = i - 1; + break; + } + } + + if (newIndex == -1) return null; + // found method call start, now we need to find the method, and check the paramCounter'th parameter of it + // we could have multiple methods with similar signature (eg one parameter, or two parameters), + // in this case we load completion for all methods. Now we need to find out where our method declaration + // starts. + int startIndex = 0; + parenCounter = 0; + int endIndex = newIndex; + for (int i = newIndex;i >= 0;i--) { + token = tokens.get(i); + // if we encounter any operator, comma or new keyword, or a ( without a ) we stop + if ((token.getType() & TokenTypes.OPERATOR) > 0 || (token.getType() & TokenTypes.ASSIGNMENT_OPERATOR) > 0 || token.getType() == TokenTypes.KEYWORD_NEW || token.getType() == TokenTypes.SEPARATOR_COMMA) { + startIndex = i + 1; + break; + } + else if (token.getType() == TokenTypes.SEPARATOR_RPAREN) parenCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter > 0) parenCounter--; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter == 0) { + startIndex = i + 1; + break; + } + } + + if (startIndex <= endIndex) { + // now the first token at startIndex should be the variable name. If startindex == endindex, this + // should already be a method call + if (startIndex == endIndex) { + // search for a method in the current CU with matching signature + TypeDeclaration deepestTd = cu.getDeepestTypeDeclarationAtOffset(comp.getCaretPosition()); + TypeDeclaration td = deepestTd; + boolean foundMethodcall = false; + while (td != null) { + List varTypes = findMethodsParameterForType(cu, new Type(td.getName(true)), tokens.get(startIndex).getLexeme(), paramCounter + 1); + if (varTypes != null && varTypes.size() > 0) { + foundMethodcall = true; + result.addAll(varTypes); + } + td = td.getParentType(); + } + + // if we did not found any matching method call, this might be a constructor for a class file + if (!foundMethodcall && deepestTd != null) { + Type type = resolveType2(cu, tokens.get(startIndex).getLexeme(), deepestTd, findCurrentMethod(deepestTd, comp.getCaretPosition()), tokens.get(startIndex).getLexeme(), comp.getCaretPosition()); + if (type != null) { + // get the possible parameter types for the given method + List varTypes = findMethodsParameterForType(cu, type, tokens.get(startIndex).getLexeme(), paramCounter + 1); + if (varTypes != null) { + result.addAll(varTypes); + } + } + } + } + else { + token = tokens.get(startIndex); + String varName = token.getLexeme(); + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(comp.getCaretPosition()); + if (td != null) { + // have a variable type, we should now follow the method calls till we reach to the end + List methods = new ArrayList(); + StringBuilder sb = new StringBuilder(); + for (int i = startIndex + 2;i <= endIndex;i++) { + token = tokens.get(i); + if (token.getType() != TokenTypes.SEPARATOR_DOT) { + sb.append(token.getLexeme()); + } + else { + methods.add(sb.toString()); + sb = new StringBuilder(); + } + } + // this should be the last part + if (sb.length() > 0) { + methods.add(sb.toString()); + } + + // build a method call except the last call + sb = new StringBuilder(varName); + for (int i = 0;i < methods.size()-1;i++) { + sb.append('.'); + sb.append(methods.get(i)); + } + + Type type = resolveType2(cu, sb.toString(), td, findCurrentMethod(td, comp.getCaretPosition()), sb.toString(), comp.getCaretPosition()); + if (type != null) { + // get the possible parameter types for the given method + List varTypes = findMethodsParameterForType(cu, type, methods.get(methods.size() - 1), paramCounter + 1); + if (varTypes != null) { + result.addAll(varTypes); + } + } + } + } + } + + return result; + } + + /** + * Process current document element into a token list + * + * @param comp + * @return + */ + private List getTokenListForLine(JTextComponent comp) { + Document doc = comp.getDocument(); + + Element root = doc.getDefaultRootElement(); + int index = root.getElementIndex(comp.getCaretPosition()); + Element elem = root.getElement(index); + int start = elem.getStartOffset(); + int len = comp.getCaretPosition() - start; + Segment seg = new Segment(new char[10000], 0, 0); + try { + doc.getText(start, len, seg); + } catch (Exception ble) { + ble.printStackTrace(); + return null; + } + + Scanner scanner = new Scanner(new StringReader(seg.toString())); + List tokens = new ArrayList(); + org.fife.rsta.ac.java.rjc.lexer.Token token; + try { + while ((token = scanner.yylex()) != null) { + tokens.add(token); + } + } + catch (IOException e) { + e.printStackTrace(); + } + + return tokens; + } + + /** + * Checks if the current cursor position is in a method call parameter position. If yes, it tries to + * determine the type of that parameter the cursor is currently in, and tries to load completions according + * to the determined type (or types in case multiple methods found with similar signature) + * + * @param cu + * @param retVal + * @param comp + * @return + */ + private boolean isInMethodParameter(CompilationUnit cu, Set retVal, JTextComponent comp) { + org.fife.rsta.ac.java.rjc.lexer.Token token; + List tokens = getTokenListForLine(comp); + if (tokens == null) return false; + + // get the position for the first , or first (. If we don't encounter a , or a ( without a having ) we are not in a method parameter + if (tokens.size() > 0) { + int parenCounter = 0; + int newIndex = -1; + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + if (token.getType() == TokenTypes.SEPARATOR_COMMA) { + // preserve the comma, so paramcounter will properly increased + newIndex = i; + break; + } + // if we encounter a dot, and we are not in an embedded method call, we break, this is no more a method param completion + else if (token.getType() == TokenTypes.SEPARATOR_DOT && parenCounter == 0) { + return false; + } + else if (token.getType() == TokenTypes.SEPARATOR_RPAREN) parenCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter > 0) parenCounter--; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter == 0) { + // preserve the lparen, so method boundary will be perfectly matched + newIndex = i; + break; + } + } + + if (newIndex > -1) { + List varTypes = getMethodCallParameterTypes(cu, tokens.subList(0, newIndex + 1), comp); + if (varTypes != null && varTypes.size() > 0) { + // load completions for given type searching for available local vars, fields, and method return values + for (Type t : varTypes) { + loadCompletionsForType(cu, retVal, t, comp); + } + return true; + } + } + } + + return false; + } + + /** + * Checks if the new keyword is in a method call parameter (eg. "list.add(new "). If so, it tries to + * find the method and get the type of the parameter the new keyword is at. If it finds, it adds constructor + * completions to that parameter type. + * + * @param cu + * @param retVal + * @param comp + * @return + */ + private boolean newIsInMethodParameter(CompilationUnit cu, Set retVal, JTextComponent comp) + { + org.fife.rsta.ac.java.rjc.lexer.Token token; + List tokens = getTokenListForLine(comp); + if (tokens == null) return false; + + int newIndex = -1; + if (tokens.size() > 0) { + // check backward the tokens check if we encounter a new keyword + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + // if we have the new keyword this will be the start of our further checks + if (token.getType() == TokenTypes.KEYWORD_NEW ) { + newIndex = i - 1; + break; + } + } + + if (newIndex > -1) { + List varTypes = getMethodCallParameterTypes(cu, tokens.subList(0, newIndex + 1), comp); + + if (varTypes != null && varTypes.size() > 0) { + for (Type t : varTypes) { + loadConstructorsForType(cu, retVal, t, false); + } + return true; + } + } + } + + return false; + } + + /** + * Finds a given method with methodName and with at least paramCounter number of parameters + * in the given type. Type can be an external ClassFile or an internal type declaration. + * This will return the type of the parameter at the given parameter index (paramCounter) + * + * @param type + * @param methodName + * @param paramCounter + * @return + */ + private List findMethodsParameterForType(CompilationUnit cu, Type type, String methodName, int paramCounter) { + List retval = new ArrayList(); + List result = new ArrayList(); + searchForClassFilesWithType(cu, result, type, false); + if (result.size() > 0) { + // we managed to find at least one class with matching type. Check for methodName. + for (int i = 0;i < result.size();i++) { + Object o = result.get(i); + if (o instanceof ClassFile) { + ClassFile cf = (ClassFile) o; + cf.setTypeParamsToTypeArgs(createTypeParamMap(type, cf)); + List methodInfos = findMethods(cf, methodName, paramCounter); + if (methodInfos != null) { + for (MethodInfo methodInfo : methodInfos) { + retval.add(new Type(methodInfo.getParameterType(paramCounter - 1, true))); + } + } + } + else if (o instanceof NormalClassDeclaration) { + TypeDeclaration td = (TypeDeclaration) o; + List methods = findMethods(cu, (NormalClassDeclaration) td, createTypeParamMap(type, td), methodName, paramCounter); + if (methods != null) { + for (Object obj : methods) { + if (obj instanceof Method) { + retval.add(((Method) obj).getParameter(paramCounter - 1).getType()); + } + else if (obj instanceof MethodInfo) { + retval.add(new Type(((MethodInfo) obj).getParameterType(paramCounter - 1, true))); + } + } + } + } + } + + } + return retval; + } + + /** + * Finds a given method with methodName and with at least paramCounter number of parameters + * in the given type. Type can be an external ClassFile or an internal type declaration. + * It will return the method/methodInfo object + * + * @param type + * @param methodName + * @param paramCounter + * @return + */ + private List findMethodsForType(CompilationUnit cu, Type type, String methodName, int paramCounter) { + List retval = new ArrayList(); + List result = new ArrayList(); + searchForClassFilesWithType(cu, result, type, false); + if (result.size() > 0) { + // we managed to find at least one class with matching type. Check for methodName. + for (int i = 0;i < result.size();i++) { + Object o = result.get(i); + if (o instanceof ClassFile) { + ClassFile cf = (ClassFile) o; + cf.setTypeParamsToTypeArgs(createTypeParamMap(type, cf)); + List methodInfos = findMethods(cf, methodName, paramCounter); + if (methodInfos != null) { + for (MethodInfo methodInfo : methodInfos) { + retval.add(methodInfo); + } + } + } + else if (o instanceof NormalClassDeclaration) { + TypeDeclaration td = (TypeDeclaration) o; + List methods = findMethods(cu, (NormalClassDeclaration) td, createTypeParamMap(type, td), methodName, paramCounter); + if (methods != null) { + for (Object obj : methods) { + if (obj instanceof Method) { + retval.add(obj); + } + else if (obj instanceof MethodInfo) { + retval.add(obj); + } + } + } + } + } + + // if not found, check if we can find a public inner class declaration + if (retval.size() == 0) { + for (int i = 0;i < result.size();i++) { + Object o = result.get(i); + if (o instanceof NormalClassDeclaration) { + TypeDeclaration td = (TypeDeclaration) o; + TypeDeclaration innerTd = getTypeDeclarationForInnerType(td, new Type(methodName)); + // find constructors with at least paramCount number of parameters + if (innerTd != null && innerTd.getModifiers().isPublic()) { + List methods = innerTd.getMethodsByName(methodName); + for (int j = 0;methods != null && j < methods.size();j++) { + if (methods.get(j).getParameterCount() >= paramCounter) { + retval.add(methods.get(j)); + } + } + } + } + // if not found, check if we can find a public inner class declaration in classfiles + else if (o instanceof ClassFile) { + ClassFile innerCf = getClassFileFor(cu, type.getName(true, false) + "$" + methodName); + if (innerCf != null && (innerCf.getAccessFlags() & AccessFlags.ACC_PUBLIC) > 0) { + // found a proper innerclass definition, load constructors + List methods = innerCf.getMethodInfoByNameAndMinimalArguments(innerCf.getClassName(false), paramCounter); + for (int j = 0;methods != null && j < methods.size();j++) { + retval.add(methods.get(j)); + } + } + } + } + } + } + + return retval; + } + + /** + * Checks whether a return keyword exists right before the new keyword + * + * @param comp + * @return + */ + private boolean hasReturnKeywordBeforeNew(JTextComponent comp) { + org.fife.rsta.ac.java.rjc.lexer.Token token; + List tokens = getTokenListForLine(comp); + if (tokens == null) return false; + + if (tokens.size() > 0) { + // check backward the tokens check if we encounter a new keyword. A ) or a ( will stop the search with result false + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + // if we have the new keyword, and the prev token is a = sign, we can check for a variable token before the = sign + if (token.getType() == TokenTypes.KEYWORD_NEW && i > 0 && tokens.get(i - 1).getType() == TokenTypes.KEYWORD_RETURN) { + return true; + } + } + } + + return false; + } + + /** + * Returns the variable name before the new keyword if a x = new is entered (checks for the = sign before the new) + * + * @param comp + * @return + */ + private String getVariableBeforeNew(JTextComponent comp) { + org.fife.rsta.ac.java.rjc.lexer.Token token; + List tokens = getTokenListForLine(comp); + if (tokens == null) return null; + + if (tokens.size() > 0) { + // check backward the tokens check if we encounter a new keyword. A ) or a ( will stop the search with result false + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + // if we have the new keyword, and the prev token is a = sign, we can check for a variable token before the = sign + if (token.getType() == TokenTypes.KEYWORD_NEW && i > 1 && tokens.get(i - 1).getType() == TokenTypes.OPERATOR_EQUALS && (tokens.get(i - 2).getType() & TokenTypes.OPERATOR) == 0) { + return tokens.get(i - 2).getLexeme(); + } + } + } + + return null; + } + + /** + * Checks if a new keyword is present right before the cursor + * + * @param comp + * @return + */ + private boolean hasNewKeyword(JTextComponent comp) { + org.fife.rsta.ac.java.rjc.lexer.Token token; + List tokens = getTokenListForLine(comp); + if (tokens == null) return false; + + if (tokens.size() > 0) { + // check backward the tokens check if we encounter a new keyword. A ) or a ( will stop the search with result false + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + if (token.getType() == TokenTypes.SEPARATOR_LPAREN || token.getType() == TokenTypes.SEPARATOR_RPAREN) return false; + if (token.getType() == TokenTypes.KEYWORD_NEW) return true; + } + } + + return false; + } + + /** + * Loads completions for the given type. This should search for any local var, field and method declaration + * which type is compatible (assignable) to given paramType + * @param cu + * @param retVal + * @param paramType + */ + private void loadCompletionsForType(CompilationUnit cu, Set retVal, Type paramType, JTextComponent comp) { + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(comp.getCaretPosition()); + while (td != null) { + Method m = findCurrentMethod(td, comp.getCaretPosition()); + if (m != null) { + // check for local vars in the deepest codeblock containing the cursor, and the go up in the codeblock + // hierarchy + CodeBlock block = m.getBody().getDeepestCodeBlockContaining(comp.getCaretPosition()); + while (block != null) { + for (int i = 0;i < block.getLocalVarCount();i++) { + LocalVariable localVariable = block.getLocalVar(i); + if (SourceParamChoicesProvider.isTypeCompatible(cu, localVariable.getType(), paramType.getName(true, false), jarManager)) { + retVal.add(new LocalVariableCompletion(this, localVariable)); + } + } + block = block.getParent(); + } + + // check local vars in method + for (int i = 0;i < m.getBody().getLocalVarCount();i++) { + LocalVariable localVariable = m.getBody().getLocalVar(i); + if (SourceParamChoicesProvider.isTypeCompatible(cu, localVariable.getType(), paramType.getName(true, false), jarManager)) { + retVal.add(new LocalVariableCompletion(this, localVariable)); + } + } + + // check method params + for (int i = 0;i < m.getParameterCount();i++) { + FormalParameter param = m.getParameter(i); + if (SourceParamChoicesProvider.isTypeCompatible(cu, param.getType(), paramType.getName(true, false), jarManager)) { + retVal.add(new LocalVariableCompletion(this, param)); + } + } + } + + // now check methods and fields + for (int i = 0;i < td.getMemberCount();i++) { + if (td.getMember(i) instanceof Method) { + Method method = (Method) td.getMember(i); + // do not check for constructors + if (!method.isConstructor()) { + if (SourceParamChoicesProvider.isTypeCompatible(cu, method.getType(), paramType.getName(true, false), jarManager)) { + retVal.add(new MethodCompletion(this, method)); + } + } + } + else if (td.getMember(i) instanceof Field) { + Field field = (Field) td.getMember(i); + if (SourceParamChoicesProvider.isTypeCompatible(cu, field.getType(), paramType.getName(true, false), jarManager)) { + retVal.add(new FieldCompletion(this, field)); + } + } + } + + // if the td is not static (eg. not a static inner class) we go up in the class hierarchy to access enclosing + // class methods, fields, etc. Otherwise this is the end of processing + if (!td.isStatic()) { + td = td.getParentType(); + } + else td = null; + } + } + + /** + * This method does a complex matching against the text and completions. It also + * checks Uppercase character parts. Eg. text AccU will match AccountingUtil and + * AccessUtil but will not match Accessutils + * + * @param completions + * @param text + * @return + */ + private List getCompletionSublist(List completions, String text) { + // if the search text is empty, return the whole list + if (text == null || text.equals("")) return completions; + + List result = new ArrayList(); + // get text parts for the search text + List textParts = org.fife.ui.autocomplete.Util.getTextParts(text); + + for (Completion completion : completions) { + // get text parts for current completion + List completionParts = org.fife.ui.autocomplete.Util.getTextParts(completion.getInputText()); + + // check if the parts of the completion starts with the parts of the search text + if (org.fife.ui.autocomplete.Util.matchTextParts(textParts, completionParts)) result.add(completion); + } + + return result; + } + + @Override + public String getAlreadyEnteredText(JTextComponent comp) { + String result = getAlreadyEnteredTextS2(comp, comp.getCaretPosition()); + + // go back to the first opening ( without a matching closing ) + int pos = 0; + int parentCounter = 0; + for (int i = result.length() - 1;i >= 0;i--) + { + char c = result.charAt(i); + if (c == ')') parentCounter++; + else if (c == '(') parentCounter--; + if (parentCounter < 0) + { + pos = i + 1; + break; + } + } + return result.substring(pos); + } - @SuppressWarnings("unchecked") - int start = Collections.binarySearch(completions, text, comparator); - if (start<0) { - start = -(start+1); - } - else { - // There might be multiple entries with the same input text. - while (start>0 && - comparator.compare(completions.get(start-1), text)==0) { - start--; - } - } + public static String getAlreadyEnteredTextS2(JTextComponent comp, int dot) { - @SuppressWarnings("unchecked") - int end = Collections.binarySearch(completions, text+'{', comparator); - end = -(end+1); + Document doc = comp.getDocument(); - return completions.subList(start, end); + Element root = doc.getDefaultRootElement(); + int index = root.getElementIndex(dot); + Element elem = root.getElement(index); + int start = elem.getStartOffset(); + int len = dot - start; + Segment seg = new Segment(new char[10000], 0, 0); + try { + doc.getText(start, len, seg); + } catch (Exception ble) { + ble.printStackTrace(); + return EMPTY_STRING; + } - } finally { - comp.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + int segEnd = seg.offset + len; + start = segEnd - 1; + while (start >= seg.offset && isValidChar2(seg.array[start])) { + start--; } + start++; - } + len = segEnd - start; + if (len == 0) + return EMPTY_STRING; + String result = new String(seg.array, start, len); + result = result.trim().replace("\t", ""); + return result; + } /** * Returns the jars on the "build path." @@ -605,9 +1462,9 @@ public List getJars() { -public SourceLocation getSourceLocForClass(String className) { - return jarManager.getSourceLocForClass(className); -} + public SourceLocation getSourceLocForClass(String className) { + return jarManager.getSourceLocForClass(className); + } /** * Returns whether a method defined by a super class is accessible to @@ -618,7 +1475,9 @@ public SourceLocation getSourceLocForClass(String className) { * @return Whether or not the method is accessible. */ private boolean isAccessible(MemberInfo info, String pkg) { - + if (loadPrivateMemberAlways) { + return true; + } boolean accessible = false; int access = info.getAccessFlags(); @@ -636,6 +1495,15 @@ else if (org.fife.rsta.ac.java.classreader.Util.isDefault(access)) { } + protected static boolean isValidChar2(char ch) { + boolean res = ch != ';' && ch != '=' && ch != '{' && ch != '}' && + ch != ' ' && ch != ',' && ch != '<' && ch != '>' + && ch != '-' && ch != '+' && ch != '/' && ch != '!'; +// if (!res) { + // System.out.println(ch); +// } + return res; + } /** * {@inheritDoc} @@ -676,31 +1544,62 @@ private void loadCompletionsForCaretPosition(CompilationUnit cu, int lastDot = alreadyEntered.lastIndexOf('.'); boolean qualified = lastDot>-1; - String prefix = qualified ? alreadyEntered.substring(0, lastDot) : null; - - Iterator i = cu.getTypeDeclarationIterator(); - while (i.hasNext()) { - - TypeDeclaration td = i.next(); - start = td.getBodyStartOffset(); - end = td.getBodyEndOffset(); - - if (caret>start && caret<=end) { - loadCompletionsForCaretPosition(cu, comp, alreadyEntered, - retVal, td, prefix, caret); - } - - else if (caret tokens = new ArrayList(); + org.fife.rsta.ac.java.rjc.lexer.Token token; + try { + while ((token = scanner.yylex()) != null) { + tokens.add(token); + } + } + catch (IOException e) { + e.printStackTrace(); + } + if (tokens.size() > 0) { + // go back through the tokens till we find a whitespace or brace, take attention on method calls + int rparenCounter = 0; + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + if (token.isType(TokenTypes.SEPARATOR_RPAREN)) rparenCounter++; + if (token.isType(TokenTypes.SEPARATOR_LPAREN) && rparenCounter == 0) { + // we found an lparen without having a matching closing rparen, this should be the start of our delcaration + declarationStart = token.getOffset() + token.getLength(); + break; + } + else if (token.isType(TokenTypes.SEPARATOR_LPAREN)) rparenCounter--; + } + } + if (declarationStart > lastDot) declarationStart = 0; + String prefix = qualified ? alreadyEntered.substring(declarationStart, lastDot) : null; + + TypeDeclaration deepestTd = cu.getDeepestTypeDeclarationAtOffset(caret); + if (deepestTd != null) { + loadCompletionsForCaretPosition(cu, comp, alreadyEntered, retVal, deepestTd, prefix, caret); + } //long time = System.currentTimeMillis() - startTime; //System.out.println("methods/fields/localvars loaded in: " + time); } + public static Method findCurrentMethod(TypeDeclaration td, int caret) { + Iterator j = td.getMemberIterator(); + while (j.hasNext()) { + Member m = j.next(); + if (m instanceof Method) { + Method method = (Method) m; + if (caret >= method.getBodyStartOffset() && caret < method.getBodyEndOffset()) { + // Don't add completions for local vars if there is + // a prefix, even "this". + return method; + } + } + } + return null; + } /** * Loads completions based on the current caret location in the source. @@ -726,15 +1625,41 @@ private void loadCompletionsForCaretPosition(CompilationUnit cu, JTextComponent comp, String alreadyEntered, Set retVal, TypeDeclaration td, String prefix, int caret) { + // used to determine if this. is used which is our enclosing class + TypeDeclaration deepestTd = cu.getDeepestTypeDeclarationAtOffset(caret); + + // load completions for the deepest type declaration if the prefix is null + if (prefix == null && td == deepestTd) { + Map typeParamMap = new HashMap(); + if (td instanceof NormalClassDeclaration) { + NormalClassDeclaration ncd = (NormalClassDeclaration)td; + List typeParams = ncd.getTypeParameters(); + if (typeParams!=null) { + for (TypeParameter typeParam : typeParams) { + String typeVar = typeParam.getName(); + // For non-qualified completions, use type var name. + typeParamMap.put(typeVar, typeVar); + } + } + } + addCompletionsForInnerClass(retVal, cu, deepestTd, typeParamMap, false); + } + // Do any child types first, so if any vars, etc. have duplicate names, // we pick up the one "closest" to us first. - for (int i=0; i typeParamMap = new HashMap(); if (td instanceof NormalClassDeclaration) { @@ -752,51 +1677,83 @@ private void loadCompletionsForCaretPosition(CompilationUnit cu, // Get completions for this class's methods, fields and local // vars. Do this before checking super classes so that, if // we overrode anything, we get the "newest" version. - String pkg = cu.getPackageName(); - Iterator j = td.getMemberIterator(); - while (j.hasNext()) { - Member m = j.next(); - if (m instanceof Method) { - Method method = (Method)m; - if (prefix==null || THIS.equals(prefix)) { - retVal.add(new MethodCompletion(this, method)); - } - if (caret>=method.getBodyStartOffset() && caret j = td.getMemberIterator(); + while (j.hasNext()) { + Member m = j.next(); + if (m instanceof Method) { + Method method = (Method) m; + retVal.add(new MethodCompletion(this, method)); +// if (caret>=method.getBodyStartOffset() && caret 0) { + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + addCompletionsForDefinedClasses(retVal, cu.getTypeDeclaration(i)); + } + } + } + // Completions for methods of fields, return values of methods, // static fields/methods, etc. if (prefix!=null && !THIS.equals(prefix)) { @@ -804,9 +1761,1179 @@ else if (m instanceof Field) { alreadyEntered, retVal, td, currentMethod, prefix, caret); } - + } + + /** + * Checks if the given ClassFile is assignable from the given paramType. Checks if the given classFile extends + * the given paramType (hierarchical back till java.lang.Object) or implements in an interface the given paramType + * Returns true, if the given classFile extends or implements the given paramType, false otherwise + * + * @param jm + * @param variableTypeCf + * @param paramType + * @return + */ + private boolean checkTypeInClassFiles(JarManager jm, ClassFile variableTypeCf, String paramType) { + if (variableTypeCf != null) { + if (paramType.contains("$")) paramType = paramType.replaceAll("\\$", "\\."); + // check if paramType (parameter type) is a super class of variableType + if (paramType.equals(variableTypeCf.getSuperClassName(true))) return true; + // if not, check for superclass's superclass, etc. till Object. If we reach object, the variableType cannot be + // assigned to the parameter + ClassFile cf; + if (variableTypeCf.getSuperClassName(true) == null) cf = null; + else cf = jm.getClassEntry(variableTypeCf.getSuperClassName(true)); + while (cf != null && !cf.getClassName(true).equals(Object.class.getName())) { + if (paramType.equals(cf.getClassName(false))) return true; + cf = jm.getClassEntry(cf.getSuperClassName(true)); + } + + // if not found in the chain of superclass hierarchy, check the interfaces + for (int i = 0;i < variableTypeCf.getImplementedInterfaceCount();i++) { +// String ifName = variableTypeCf.getImplementedInterfaceName(i, true); +// if (paramType.equals(ifName)) return true; + // if not found in this interface, check if this interface extends the typeName + cf = jm.getClassEntry(variableTypeCf.getImplementedInterfaceName(i, true)); + // interfaces can only have superclasses (super interfaces) + while (cf != null) { + if (paramType.equals(cf.getClassName(true))) return true; + if (cf.getSuperClassName(true) == null) cf = null; + else cf = jm.getClassEntry(cf.getSuperClassName(true)); + } + } + } + + return false; + } + + /** + * This method checks if a variable type declared in the current compilation unit is assignable to the paramType. + * Checks super classes till it reaches java.lang.Object, and checks implemented interfaces for a match. + * Returns true, if the given class (TypeDeclaration) extends or implements the given paramType, false otherwise + * + * @param cu + * @param td + * @param jm used to pass to the checkTypeInClassFiles method if a local class's superclass is outside this CU + * @param paramType + * @return + */ + private boolean checkTypeInTypeDeclaration(CompilationUnit cu, TypeDeclaration td, JarManager jm, String paramType) { + // check in the local compilation unit if we find a matching type + if (td instanceof NormalClassDeclaration) { + NormalClassDeclaration ncd = (NormalClassDeclaration) td; + // found matching typeDeclaration + // check if superclass or implemented interfaces do match the paramType + String fqSuperclass = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jm, ncd.getExtendedType().getName(true, false)); + if (paramType.equals(fqSuperclass)) return true; + // otherwise we should search for superclass's superclass, etc. check first the class files + boolean result = checkTypeInClassFiles(jm, getClassFileFor(cu, fqSuperclass), paramType); + if (result) return true; + + // not found in superclass, check the interfaces + if (ncd.getImplementedCount() > 0) { + Iterator implIterator = ncd.getImplementedIterator(); + while (implIterator.hasNext()) { + Type implType = implIterator.next(); + String fqInterface = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jm, implType.getName(true, false)); + // if the paramType matches the interface, we accept + if (fqInterface.equals(paramType)) return true; + // otherwise check if the interface class file has some extending interfaces which might match + result = checkTypeInClassFiles(jm, getClassFileFor(cu, fqInterface), paramType); + if (result) return true; + // check if this interface is defined inside the cu + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + TypeDeclaration innerType = getTypeDeclarationForInnerType(cu.getTypeDeclaration(i), implType); + if (innerType != null) { + result = checkTypeInTypeDeclaration(cu, innerType, jm, paramType); + if (result) return true; + } + } + } + } + +// // check child td's +// if (ncd.getChildTypeCount() > 0) { +// for (int i = 0;i < ncd.getChildTypeCount();i++) { +// result = checkTypeInTypeDeclaration(cu, ncd.getChildType(i), jm, paramType); +// if (result) return true; +// } +// } + } + // if interface declaration + else if (td instanceof NormalInterfaceDeclaration) { + NormalInterfaceDeclaration nitd = (NormalInterfaceDeclaration) td; + // found if declaration for variable, check the extending classes + Iterator extTypes = nitd.getExtendedIterator(); + while (extTypes.hasNext()) { + Type t = extTypes.next(); + String typeName = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jm, t.getName(true, false)); + if (typeName.equals(paramType)) return true; + // check extended interfaces + boolean result = checkTypeInClassFiles(jm, getClassFileFor(cu, typeName), paramType); + if (result) return true; + } + } + + return false; + } + + /** + * Searches for classFiles or TypeDeclarations for the given type + * @param cu + * @param result + * @param type + */ + private void searchForClassFilesWithType(CompilationUnit cu, List result, Type type, boolean forSuper) { + String fqTypeName = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jarManager, type.getName(true, false)); + ClassFile cf = jarManager.getClassEntry(fqTypeName); + if (cf != null) { + if (fqTypeName.contains("$")) fqTypeName = fqTypeName.replaceAll("\\$", "\\."); + if (cf.getClassName(true).equals(fqTypeName) || (!forSuper && checkTypeInClassFiles(jarManager, cf, fqTypeName))) { + result.add(cf); + } + } +// for (ImportDeclaration imp : cu.getImports()) { +// List cfs = null; +// if (imp.isWildcard()) { +// cfs = jarManager.getClassesInPackage(imp.getName().substring(0, imp.getName().length() - 2), false); +// } +// else { +// cfs = new ArrayList(); +// cfs.add(jarManager.getClassEntry(imp.getName())); +// } +// +// for (int i = 0;cfs != null && i < cfs.size();i++) { +// ClassFile cf = cfs.get(i); +// if (cf.getClassName(true).equals(fqTypeName) || (!forSuper && checkTypeInClassFiles(jarManager, cf, fqTypeName))) { +// result.add(cf); +// } +// } +// } +// // check java.lang package +// List cfs = jarManager.getClassesInPackage("java.lang", false); +// for (int i = 0;cfs != null && i < cfs.size();i++) { +// ClassFile cf = cfs.get(i); +// if (cf.getClassName(true).equals(fqTypeName) || (!forSuper && checkTypeInClassFiles(jarManager, cf, fqTypeName))) { +// result.add(cf); +// } +// } + + if (cu != null && cu.getTypeDeclarationCount() > 0) { + for (int i = 0; i < cu.getTypeDeclarationCount(); i++) { + TypeDeclaration foundDeclaration = findTypeInTypeDeclaration(cu, cu.getTypeDeclaration(i), fqTypeName, forSuper); + if (foundDeclaration != null) result.add(foundDeclaration); + } + } + } + + /** + * Checks if the given type is extended or implemented by any of the declared classes in the given TypeDeclaration + * @param cu + * @param td + * @param type + * @return + */ + private TypeDeclaration findTypeInTypeDeclaration(CompilationUnit cu, TypeDeclaration td, String type, boolean forSuper) { + String fqTypeName = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jarManager, type); + if (td.getName(true).equals(fqTypeName) || (!forSuper && checkTypeInTypeDeclaration(cu, td, jarManager, type))) { + return td; + } + if (td.getChildTypeCount() > 0) { + for (int i = 0;i < td.getChildTypeCount();i++) { + TypeDeclaration childTd = td.getChildType(i); + if (childTd.getName(true).equals(fqTypeName) || (!forSuper && checkTypeInTypeDeclaration(cu, childTd, jarManager, fqTypeName))) { + return childTd; + } + } + } + return null; + } + + /** + * This method loads the constructors for the given Type. It looks for an external type, if not found, searches the + * current CU for some inner class declaration + * @param cu + * @param retVal + * @param extendedType + */ + private void loadConstructorsForType(CompilationUnit cu, Set retVal, Type extendedType, boolean forSuper) { +// String extendedTypeName = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jarManager, extendedType.getName(true, false)); + // we need to load every ClassFile/TypeDeclaration, which extend or implement this type somewhere in the object hierarchy + List classfiles = new ArrayList(); + searchForClassFilesWithType(cu, classfiles, extendedType, forSuper); + for (Object o : classfiles) { + if (o instanceof ClassFile) { + ClassFile cf = (ClassFile) o; + if ((cf.getAccessFlags() & AccessFlags.ACC_INTERFACE) == 0 && (cf.getAccessFlags() & AccessFlags.ACC_ABSTRACT) == 0) { + addConstructors(retVal, cf, forSuper); + } + } + else if (o instanceof TypeDeclaration) + { + TypeDeclaration extendedTd = (TypeDeclaration) o; + // check if we can find the class Type in this class and then load the inner class completions if any match +// TypeDeclaration extendedTd = null; +// for (int i = 0;i < cu.getTypeDeclarationCount();i++) { +// extendedTd = getTypeDeclarationForInnerType(cu.getTypeDeclaration(i), extendedType); +// if (extendedTd != null) break; +// } + if (!(extendedTd instanceof NormalInterfaceDeclaration) && !extendedTd.getModifiers().isAbstract()) { + addConstructors(retVal, extendedTd, forSuper); + } + } + else System.out.println("[DEBUG]: Couldn't find ClassFile for: " + extendedType.getName(true)); + } + } + + /** + * Loads constructors from the given ClassFile + * @param retVal + * @param cf + */ + private void addConstructors(Set retVal, ClassFile cf, boolean forSuper) { + boolean foundConstructor = false; + for (int i = 0;i < cf.getMethodCount();i++) { + if (cf.getMethodInfo(i).isConstructor()) { + retVal.add(forSuper ? MethodCompletion.createSuperConstructorCompletion(this, cf.getMethodInfo(i)) : new MethodCompletion(this, cf.getMethodInfo(i))); + foundConstructor = true; + } + } + // no constructor found, we should add a default constructor proposal + if (!foundConstructor) { + retVal.add(MethodCompletion.createDefaultConstructorCompletion(this, forSuper ? "super" : cf.getClassName(false) + (cf.getParamTypes() != null && cf.getParamTypes().size() > 0 ? "<>" : ""), new Type(cf.getClassName(true)))); + } + } + + /** + * Loads constructors from the given TypeDeclaration + * @param retVal + * @param td + */ + private void addConstructors(Set retVal, TypeDeclaration td, boolean forSuper) { + boolean foundConstructor = false; + for (int i = 0;i < td.getMemberCount();i++) { + if (td.getMember(i) instanceof Method && td.getMember(i).getType() == null) { + retVal.add(forSuper ? MethodCompletion.createSuperConstructorCompletion(this, (Method) td.getMember(i)) : new MethodCompletion(this, (Method) td.getMember(i))); + foundConstructor = true; + } + } + // no constructor found, we should add a default constructor proposal + if (!foundConstructor && !(td instanceof NormalInterfaceDeclaration) && !td.getModifiers().isAbstract()) { + retVal.add(MethodCompletion.createDefaultConstructorCompletion(this, forSuper ? "super" : td.getName(false) + (((NormalClassDeclaration) td).getTypeParameters() != null && ((NormalClassDeclaration) td).getTypeParameters().size() > 0 ? "<>" : ""), new Type(td.getName(false)))); + } + } + + private void addCompletionsForDefinedClasses(Set retVal, TypeDeclaration typeDeclaration) { + retVal.add(new TypeDeclarationCompletion(this, typeDeclaration)); + if (loadConstructors) { + addConstructors(retVal, typeDeclaration, false); + } + if (typeDeclaration.getChildTypeCount() > 0) { + for (int i = 0;i < typeDeclaration.getChildTypeCount();i++) { + addCompletionsForDefinedClasses(retVal, typeDeclaration.getChildType(i)); + } + } + } + + private static int countMethodParams(String s) { + int length = s.length(); + if (length == 0) { + return 0; + } + s = s.replace(",", ""); + int lengthAfter = s.length(); + int counttt = length - lengthAfter; + return counttt + 1; } + /** + * Finds a single method with given methodName and parameter count. This will return the first match. + * + * @param cf + * @param methodName + * @param paramCount + * @return + */ + private MethodInfo findMethod(ClassFile cf, String methodName, int paramCount) { + List methodInfoByName = cf.getMethodInfoByName(methodName, paramCount); + if (methodInfoByName == null || methodInfoByName.size() == 0) { + //System.out.println("can't find methods in "); + String superClass = cf.getSuperClassName(true); + if(superClass!=null) { + { + ClassFile classEntry = jarManager.getClassEntry(superClass); + if (classEntry != null) { + MethodInfo mi = findMethod(classEntry, methodName, paramCount); + if (mi != null) { + return mi; + } + } + } + } + int count = cf.getImplementedInterfaceCount(); + for (int j = 0; j < count; j++) { + String implementedInterfaceName = cf.getImplementedInterfaceName(j, true); + ClassFile classEntry = jarManager.getClassEntry(implementedInterfaceName); + if (classEntry != null) { + MethodInfo mi = findMethod(classEntry, methodName, paramCount); + if (mi != null) { + return mi; + } + } + } + return null; + } + if (methodInfoByName.size() == 0) { + // System.out.println(methodName); + return null; + } + if (methodInfoByName.size() > 1) { + // System.out.println(methodInfoByName.size() + " " + methodName); + } + + MethodInfo methodInfo = methodInfoByName.get(0); + return methodInfo; + } + + /** + * Find all methods matching the given name and the number of parameters in the given ClassFile. + * It will also return methods, which have more parameters as the indicated, since the paramCount + * contains rather the index of the current parameter than the actual required parameter count. + * + * @param cf + * @param methodName + * @param paramCount + * @return + */ + private List findMethods(ClassFile cf, String methodName, int paramCount) { + List methodInfoByName = cf.getMethodInfoByNameAndMinimalArguments(methodName, paramCount); + if (methodInfoByName == null) { + //System.out.println("can't find methods in "); + String superClass = cf.getSuperClassName(true); + if(superClass!=null) { + ClassFile classEntry = jarManager.getClassEntry(superClass); + if (classEntry != null) { + List mi = findMethods(classEntry, methodName, paramCount); + if (mi != null) { + return mi; + } + } + } + int count = cf.getImplementedInterfaceCount(); + for (int j = 0; j < count; j++) { + String implementedInterfaceName = cf.getImplementedInterfaceName(j, true); + ClassFile classEntry = jarManager.getClassEntry(implementedInterfaceName); + if (classEntry != null) { + List mi = findMethods(classEntry, methodName, paramCount); + if (mi != null) { + return mi; + } + } + } + return null; + } + if (methodInfoByName.size() == 0) { + // System.out.println(methodName); + return null; + } + + return methodInfoByName; + } + + /** + * In case a Generic letter changes in the class hierarchy (eg MyList extends ArrayList where + * the generic in the ArrayList classFile will point to generic E instead of generic T, so we need to translate + * the letters. This method does this. The typeParamMap should be a LinkedHashMap to preserve type parameter order. + * @param cf + * @param typeParamMap + * @return + */ + private Map translateBetweenParamMaps(ClassFile cf, Map typeParamMap) { + Map superTypeParamMap = new LinkedHashMap(); + if (cf.getParamTypes() != null && typeParamMap != null) { + List superParamTypes = cf.getParamTypes(); + List paramTypes = new ArrayList(typeParamMap.values()); + for (int i = 0;i < superParamTypes.size();i++) { + String typ = superParamTypes.get(i); + // if we have a matching type, we use it + if (typeParamMap.containsKey(typ)) superTypeParamMap.put(typ, typeParamMap.get(typ)); + // else check if we have a matching pair in the original type map, if yes, use it (may be the + // type argument letter has changed + else if (i < paramTypes.size()) superTypeParamMap.put(typ, paramTypes.get(i)); + // otherwise use what provided in the superclass + else superTypeParamMap.put(typ, cf.getTypeArgument(typ)); + } + } + return superTypeParamMap; + } + + /** + * Find all methods matching the given name and the number of parameters in the given TypeDeclaration. Returns + * all methods, which have at least paramCount number of parameters + * + * @param td + * @param methodName + * @param paramCount + * @return + */ + private List findMethods(CompilationUnit cu, NormalClassDeclaration td, Map typeParamMap, String methodName, int paramCount) { + List methodInfoByName = td.getMethodsByName(methodName); + if (methodInfoByName == null || methodInfoByName.size() == 0) { + //System.out.println("can't find methods in "); + String superClass = td.getExtendedType().getName(true, false); + if(superClass!=null) { + ClassFile cf = getClassFileFor(cu, superClass); + if (cf != null) { + cf.setTypeParamsToTypeArgs(translateBetweenParamMaps(cf, typeParamMap)); + List mi = findMethods(cf, methodName, paramCount); + if (mi != null) { + return mi; + } + } + else { + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + TypeDeclaration innerTd = getTypeDeclarationForInnerType(cu.getTypeDeclaration(i), td.getExtendedType()); + if (innerTd != null) { + List methods = innerTd.getMethodsByName(methodName); + if (methods != null && methods.size() > 0) { + List result = new ArrayList(); + for (int j = 0;j < methods.size();j++) { + if (methods.get(i).getParameterCount() >= paramCount) { + result.add(methods.get(j)); + } + } + if (result.size() > 0) return result; + } + } + } + } + + } + Iterator it = td.getImplementedIterator(); + while (it.hasNext()) { + Type intf = it.next(); + ClassFile cf = getClassFileFor(cu, intf.getName(true)); + if (cf != null) { + cf.setTypeParamsToTypeArgs(translateBetweenParamMaps(cf, typeParamMap)); + List mi = findMethods(cf, methodName, paramCount); + if (mi != null) { + return mi; + } + } + else { + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + TypeDeclaration innerTd = getTypeDeclarationForInnerType(cu.getTypeDeclaration(i), intf); + if (innerTd != null) { + List methods = innerTd.getMethodsByName(methodName); + if (methods != null && methods.size() > 0) { + List result = new ArrayList(); + for (int j = 0;j < methods.size();j++) { + if (methods.get(i).getParameterCount() >= paramCount) { + result.add(methods.get(j)); + } + } + if (result.size() > 0) return result; + } + } + } + } + } + return null; + } + if (methodInfoByName.size() == 0) { + // System.out.println(methodName); + return null; + } + + List toBeRemoved = new ArrayList(); + for (int i = 0;i < methodInfoByName.size();i++) { + if (methodInfoByName.get(i).getParameterCount() < paramCount) { + toBeRemoved.add(methodInfoByName.get(i)); + } + } + methodInfoByName.removeAll(toBeRemoved); + + return methodInfoByName; + } + + private FieldInfo findField(ClassFile cf, String fieldNameS) { + FieldInfo fieldInfo = cf.getFieldInfoByName(fieldNameS); + if (fieldInfo == null) { + + String s = cf.getSuperClassName(true); + if (s != null && s.length() > 1) { + // System.out.println("can't find field for "); + ClassFile classEntry = jarManager.getClassEntry(s); + if (classEntry != null) { + FieldInfo mi = findField(classEntry, fieldNameS); + if (mi != null) { + return mi; + } + } + } + + int count = cf.getImplementedInterfaceCount(); + for (int j = 0; j < count; j++) { + String implementedInterfaceName = cf.getImplementedInterfaceName(j, true); + ClassFile classEntry = jarManager.getClassEntry(implementedInterfaceName); + if (classEntry != null) { + FieldInfo mi = findField(classEntry, fieldNameS); + if (mi != null) { + return mi; + } + } + } + return null; + } + return fieldInfo; + } + + private Type resolveTypeWithDot(CompilationUnit cu, String alreadyEntered, + TypeDeclaration td, Method currentMethod, String prefix, int offs, int dot) { + String beforeDot = prefix.substring(0, dot).trim(); + String afterDot = prefix.substring(dot + 1).trim(); + // System.out.println(beforeDot); + // System.out.println(afterDot); + Type type = resolveType2(cu, alreadyEntered, td, currentMethod, beforeDot, offs); + if (type == null) { + // System.out.println("not found " + prefix); + return null; + } + String typeStr = type.getName(true, false); + ClassFile cf = getClassFileFor(cu, typeStr); + if (cf != null) { + if (afterDot.contains("(")) { + cf.setTypeParamsToTypeArgs(createTypeParamMap(type, cf)); + int j = afterDot.indexOf("("); + String methodName = afterDot.substring(0, j).trim(); + String params = afterDot.substring(j + 1); + params = params.replace(")", "").trim(); + int countMethodParams = countMethodParams(params); + // System.out.println(methodName + " " + countMethodParams); + + MethodInfo methodInfo = findMethod(cf, methodName, countMethodParams); + String returnTypeString = methodInfo.getReturnTypeFull(); + returnTypeString = returnTypeString.replaceAll("<.+>", ""); + // System.out.println(returnTypeString + " 123"); + + Type retType = null; + if (!returnTypeString.equalsIgnoreCase("void")) { + if (returnTypeString.contains("[")) { + // possible an array, count bracket pairs. + String baseType = returnTypeString.substring(0, returnTypeString.indexOf("[")); + String brackets = returnTypeString.substring(returnTypeString.indexOf("[")); + int bracketPairs = 0; + boolean wasOpenBracket = false; + for (int i = 0;i < brackets.length();i++) { + char c = brackets.charAt(i); + if (c == '[') wasOpenBracket = true; + if (c == ']' && wasOpenBracket) { + bracketPairs++; + wasOpenBracket = false; + } + } + retType = new Type(baseType, bracketPairs); + } + else { + retType = new Type(returnTypeString); + } + } + + // cf.type + return retType; + + } else if (afterDot.contains("[")) { + // System.out.println("arrays not supported"); + return null; + } else { + FieldInfo fieldInfoByName = findField(cf, afterDot); + if (fieldInfoByName != null) { + // System.out.println("can't find fields '" + afterDot + "' , " + prefix + " " + cf.getClassName(true)); + String typeString = fieldInfoByName.getTypeString(true); + // System.out.println(typeString); + typeString = typeString.replaceAll("<.+>", ""); + return new Type(typeString); + } + // search in subtypes + ClassFile innerCf = getClassFileFor(cu, cf.getClassName(true) + "$" + afterDot); + if (innerCf != null) { + // found a classfile, which is an inner class + String pkg = innerCf.getPackageName().substring(0, innerCf.getPackageName().indexOf(innerCf.getClassName(false).substring(0, innerCf.getClassName(false).indexOf(".")))); + return new Type(pkg + innerCf.getClassName(false).replaceAll("\\.", "\\$")); + } + } + } + // we have a valid type try to load from TypeDeclaration if the current TypeDeclaration is not static + else { + TypeDeclaration innerTd = getTypeDeclarationForInnerType(cu.getTypeDeclaration(0), type); + if (innerTd == null) return null; + // check if this is accessible from current context + if (afterDot.equals(THIS)) { + if (isThisAccessible(innerTd, cu.getDeepestTypeDeclarationAtOffset(offs))) return type; + else return null; + } + // if not this entered, it can only be some static field or method + if (afterDot.contains("(")) { + int j = afterDot.indexOf("("); + String methodName = afterDot.substring(0, j).trim(); + String params = afterDot.substring(j + 1); + params = params.replace(")", "").trim(); + int countMethodParams = countMethodParams(params); + // System.out.println(methodName + " " + countMethodParams); + + Iterator methodIterator = innerTd.getMethodIterator(); + while (methodIterator.hasNext()) { + Method method = methodIterator.next(); + if (methodName.equals(method.getName()) && countMethodParams == method.getParameterCount()) { + return method.getType(); + } + } + // if not found check the super class + if (innerTd instanceof NormalClassDeclaration) { + ClassFile superCf = getClassFileFor(cu, ((NormalClassDeclaration) innerTd).getExtendedType().getName(true, false)); + superCf.setTypeParamsToTypeArgs(createTypeParamMap(type, superCf)); + if (superCf != null) { + for (int i = 0;i < superCf.getMethodCount();i++) { + MethodInfo mi = superCf.getMethodInfo(i); + if (mi.getName().equals(methodName) && mi.getParameterCount() == countMethodParams) { + return new Type(mi.getReturnTypeString(false)); + } + } + } + } + + } else if (afterDot.contains("[")) { + // System.out.println("arrays not supported"); + return null; + } else if (innerTd != null) { + Iterator fieldIterator = innerTd.getFieldIterator(); + while (fieldIterator.hasNext()) { + Field field = fieldIterator.next(); + if (afterDot.equals(field.getName())) { + return field.getType(); + } + } + } + + } + return null; + } + + /** + * This method checks if the this variable of the innerTd is accessible + * from the TypeDeclaration currently the cursor is in + * @param innerTd + * @param deepestTypeDeclarationAtOffset + * @return + */ + private boolean isThisAccessible(TypeDeclaration innerTd, TypeDeclaration deepestTypeDeclarationAtOffset) + { + // we reference the same class + if (innerTd.getName().equals(deepestTypeDeclarationAtOffset.getName())) { + return true; + } + // the class we are in is static, we cannot reference SomeClass.this + if (deepestTypeDeclarationAtOffset.isStatic()) { + return false; + } + // search the parent TypeDeclarations, till we reach a static class or reach the referenced class + TypeDeclaration parent = deepestTypeDeclarationAtOffset.getParentType(); + while (parent != null) { + if (parent.getName().equals(innerTd.getName())) return true; + if (parent.isStatic()) return false; + parent = parent.getParentType(); + } + return true; + } + + /** + * Searches and returns the TypeDeclaration object for the given inner type. The Type can be the class or + * any inner class + * @param type + * @return + */ + private TypeDeclaration getTypeDeclarationForInnerType(TypeDeclaration currentTd, Type type) { + if (currentTd.getName().equals(type.getName(false, false))) return currentTd; + if (currentTd.getChildTypeCount() > 0) { + for (int i = 0;i < currentTd.getChildTypeCount();i++) { + TypeDeclaration retval = getTypeDeclarationForInnerType(currentTd.getChildType(i), type); + if (retval != null) return retval; + } + } + return null; + } + + private void resolveType2WithDot(CompilationUnit cu, String alreadyEntered, TypeDeclaration td, + Method currentMethod, String prefix, int offs, int dot, MemberClickedListener memberClickedListener) { + String beforeDot = prefix.substring(0, dot).trim(); + String afterDot = prefix.substring(dot + 1).trim(); + // System.out.println(beforeDot); + // System.out.println(afterDot); + String typeStr = ""; + // special case, if ClassName.this. is entered, we use the class as base + if (beforeDot.equals(td.getName() + "." + THIS) && !td.isStatic()) { + typeStr = td.getName(); + } + else if (!THIS.equals(beforeDot)) { + Type type = resolveType2(cu, alreadyEntered, td, currentMethod, beforeDot, offs); + if (type == null) + { + // System.out.println("not found " + prefix); + return; + } + typeStr = type.getName(true, false); + } + else { + // find the enclosing class and use it's name as typeStr + TypeDeclaration enclosingTd = cu.getDeepestTypeDeclarationAtOffset(offs); + if (enclosingTd != null) { + typeStr = enclosingTd.getName(); + } + } + ClassFile cf = getClassFileFor(cu, typeStr); + if (cf != null) { + if (afterDot.contains("(")) { + int j = afterDot.indexOf("("); + String methodName = afterDot.substring(0, j).trim(); + String params = afterDot.substring(j + 1); + params = params.replace(")", "").trim(); + int countMethodParams = countMethodParams(params); + // System.out.println(methodName + " " + countMethodParams); + + MethodInfo methodInfo = findMethod(cf, methodName, countMethodParams); + memberClickedListener.gotoMethodInClass(cf.getClassName(true), methodInfo); + return; + } + else if (afterDot.contains("[")) { + // System.out.println("arrays not supported"); + return; + } + else { + FieldInfo fieldInfoByName = findField(cf, afterDot); + if (fieldInfoByName == null) { + // System.out.println("can't find fields '" + afterDot + "' , " + prefix + " " + cf.getClassName(true)); + return; + } + memberClickedListener.gotoFieldInClass(cf.getClassName(true), fieldInfoByName); + return; + } + } + else { + // if the type equals with the current TD, find in the current td (eg this was used) + if (typeStr.equals(td.getName())) { + if (handleClick(typeStr, afterDot, td, memberClickedListener)) return; + } + // otherwise search the inner classes + else { + for (int i = 0;i < td.getChildTypeCount();i++) { + TypeDeclaration childTd = td.getChildType(i); + if (handleClick(typeStr, afterDot, childTd, memberClickedListener)) return; + } + // search the upper classes + TypeDeclaration parent = td.getParentType(); + while (parent != null) { + if (handleClick(typeStr, afterDot, parent, memberClickedListener)) return; + parent = parent.getParentType(); + } + } + } + // System.out.println("not found : " + prefix); + } + + /** + * This method will handle the local lookup for methods / fields in + * the given TypeDeclaration. If a match is found, the + * listener is called + * @param typeStr + * @param afterDot + * @param td + * @param memberClickedListener + * @return + */ + private boolean handleClick(String typeStr, String afterDot, TypeDeclaration td, MemberClickedListener memberClickedListener) { + if (typeStr.equals(td.getName())) { + String methodName = afterDot; + int countMethodParams = 0; + + if (afterDot.contains("(")) { + int j = afterDot.indexOf("("); + methodName = afterDot.substring(0, j).trim(); + String params = afterDot.substring(j + 1); + params = params.replace(")", "").trim(); + countMethodParams = countMethodParams(params); + } + + for (int j = 0;j < td.getMemberCount();j++) { + Member member = td.getMember(j); + if (member instanceof Method && methodName.equals(member.getName()) && ((Method) member).getParameterCount() == countMethodParams) { + memberClickedListener.gotoMethod((Method) member); + return true; + } + else if (member instanceof Field && methodName.equals(member.getName())) { + memberClickedListener.gotoField((Field) member); + return true; + } + } + } + + return false; + } + + private Type resolveType2(CompilationUnit cu, String alreadyEntered, TypeDeclaration td, + Method currentMethod, String prefix, int offs) { + // String prefix2 = prefix; + int dot = prefix.indexOf('.'); + if (dot > -1) { + return resolveTypeWithDot(cu, alreadyEntered, td, currentMethod, prefix, offs, dot); + } + if (prefix.equals(td.getName())) { + return new Type(td.getName()); + } + // if super keyword, we return the super class' type + if (SUPER.equals(prefix) && td instanceof NormalClassDeclaration) { + return ((NormalClassDeclaration) td).getExtendedType(); + } + String methodName = prefix; + int countMethodParams = 0; + + if (prefix.contains("(")) { + int j = prefix.indexOf("("); + methodName = prefix.substring(0, j).trim(); + String params = prefix.substring(j + 1); + params = params.replace(")", "").trim(); + countMethodParams = countMethodParams(params); + } + for (Iterator j = td.getMemberIterator(); j.hasNext();) { + Member m = j.next(); + // The prefix might be a field in the local class. + if (m instanceof Field) { + Field field = (Field) m; + if (field.getName().equals(prefix)) { + // System.out.println("found prefix [" + prefix + "] as field in class [" + td.getName() + "]"); + return field.getType(); + } + } + // or a method call in the local class + else if (m instanceof Method) { + Method method = (Method) m; + if (method.getName().equals(methodName) && method.getParameterCount() == countMethodParams) { + // System.out.println("found prefix [" + prefix + "] as method in class [" + td.getName() + "]"); + return method.getType(); + } + } + } + if (currentMethod != null) { + for (int i = 0; i < currentMethod.getParameterCount(); i++) { + FormalParameter param = currentMethod.getParameter(i); + String name = param.getName(); + // Assuming prefix is "one level deep" and contains no '.'... + if (prefix.equals(name)) { + return param.getType(); + } + } + // check method local variables for a match + for (int i = 0;i < currentMethod.getBody().getLocalVarCount();i++) { + LocalVariable localVariable = currentMethod.getBody().getLocalVar(i); + if (prefix.equals(localVariable.getName())) { + return localVariable.getType(); + } + } + // if not found in the method body or in method parameter, try in the child code blocks + if (currentMethod.getBody().getChildBlockCount() > 0) { + CodeBlock codeBlock = currentMethod.getBody().getDeepestCodeBlockContaining(offs); + Type t = null; + while (t == null && codeBlock != null) { + t = findVariableInChildBlock(prefix, codeBlock); + codeBlock = codeBlock.getParent(); + } + if (t != null) { + return t; + } + } + } + List matchedImports = new ArrayList(); + List imports = cu.getImports(); + List matches = jarManager.getClassesWithUnqualifiedName(prefix, imports); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + String className = cf.getClassName(true); + if(className.endsWith(prefix)) { + matchedImports.add(cf); + } + } + } + if (matchedImports.size() == 1) { + // System.out.println("found matched imports "); + return new Type(matchedImports.get(0).getClassName(true)); + } + if (matchedImports.size() > 0) { + // System.out.println("found matched imports ,ore than 1"); + } else { + // System.out.println("found matched imports not found"); + } + + // System.out.println("not found " + prefix); + + // go up on the typeDeclaration hierarchy and check for every class name + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + TypeDeclaration innerTd = getTypeDeclarationForInnerType(cu.getTypeDeclaration(i), new Type(prefix)); + if (innerTd != null) { + return new Type(innerTd.getName(true)); + } + } + TypeDeclaration parent = td.getParentType(); + while (parent != null) { + if (prefix.equals(parent.getName())) { + return new Type(prefix); + } + parent = parent.getParentType(); + } + + // if not found, try within the same package (since there is no import for same package classes) + matches = jarManager.getClassesInPackage(cu.getPackageName(), false); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + String className = cf.getClassName(true); + if(className.endsWith(prefix)) { + matchedImports.add(cf); + } + } + } + if (matchedImports.size() == 1) { + // System.out.println("found matching class in package " + cu.getPackageName()); + return new Type(matchedImports.get(0).getClassName(true)); + } + + // if we reached this far, without having a match, and have a methodName, try to find it as + // inner typeDeclaration (may be it is a constructor call) + if (methodName != null) { + for (int i = 0;i < td.getChildTypeCount();i++) { + if (td.getChildType(i).getName(false).equals(methodName)) { + return new Type(td.getChildType(i).getName(false)); + } + } + // still not found, check ClassFiles for a match + ClassFile cf = getClassFileFor(cu, methodName); + if (cf != null) { + return new Type(cf.getClassName(true)); + } + } + + return null; + } + + /** + * Checks if the given variable (prefix) is available in the given codeBlock + * + * @param prefix + * @param codeBlock + * @return + */ + protected Type findVariableInChildBlock(String prefix, CodeBlock codeBlock) + { + for (int i = 0;i < codeBlock.getLocalVarCount();i++) { + LocalVariable localVariable = codeBlock.getLocalVar(i); + if (prefix.equals(localVariable.getName())) { + return localVariable.getType(); + } + } + +// if (codeBlock.getChildBlockCount() > 0) { +// for (int i = 0;i < codeBlock.getChildBlockCount();i++) { +// return findVariableInChildBlock(prefix, codeBlock.getChildBlock(i)); +// } +// } + + return null; + } + + void open(CompilationUnit cu, String alreadyEntered, TypeDeclaration td, + Method currentMethod, String prefix, int offs, int clickOffs, MemberClickedListener memberClickedListener) { + // String prefix2 = prefix; + // check for last dot before the click position + int dot = prefix.substring(0, clickOffs).lastIndexOf("."); + if (dot > -1) { + // extract method call from the prefix (we might be in a method call already) + // go backwards from the dot position and check if we have an opening ( without a closing ) + // this means we reached a method call start, so this object clicked ends here + int pos = 0; + int parenCounter = 0; + for (int i = dot;i >= 0;i--) + { + char c = prefix.charAt(i); + if (c == ')') parenCounter++; + else if (c == '(') parenCounter--; + if (parenCounter < 0) + { + pos = i + 1; + break; + } + } + prefix = prefix.substring(pos); + // subtract the position from the offset and dot positions + offs -= pos; + dot -= pos; + resolveType2WithDot(cu, alreadyEntered, td, currentMethod, prefix, offs, dot, memberClickedListener); + return; + } + List matchedImports = new ArrayList(); + List imports = cu.getImports(); + List matches = jarManager.getClassesWithUnqualifiedName(prefix, imports); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + String className = cf.getClassName(true); + if (className.endsWith(prefix)) { + matchedImports.add(cf); + } + } + } + if (matchedImports.size() == 1) { + // System.out.println("found matched imports "); + memberClickedListener.openClass(matchedImports.get(0).getClassName(true)); + return; + } + if (matchedImports.size() > 0) { + // System.out.println("found matched imports ,ore than 1"); + } else { + // check if the clicked member is a local inner class + TypeDeclaration tmpTd = td; + do { + for (int i = 0; i < tmpTd.getChildTypeCount(); i++) { + if (prefix.equals(tmpTd.getChildType(i).getName())) { + // found inner class + memberClickedListener.gotoInnerClass(tmpTd.getChildType(i)); + return; + } + // this could be a constructor? + if (prefix.contains("(")) { + String methodName = prefix; + int paramCount = 0; + if (prefix.contains("(")) { + int j = prefix.indexOf("("); + methodName = prefix.substring(0, j).trim(); + String params = prefix.substring(j + 1); + params = params.replace(")", "").trim(); + paramCount = countMethodParams(params); + } + + Iterator members = tmpTd.getChildType(i).getMemberIterator(); + while (members.hasNext()) { + Member m = members.next(); + if (m instanceof Method && m.getType() == null) { + Method method = (Method) m; + if (method.getName().equals(methodName) && method.getParameterCount() == paramCount) { + memberClickedListener.gotoMethod(method); + return; + } + } + } + } + } + // check if clicked member is a parent class of this TypeDeclaration + TypeDeclaration parent = td.getParentType(); + while (parent != null) { + if (parent.getName().equals(prefix)) { + memberClickedListener.gotoInnerClass(parent); + return; + } + // this could be a constructor? + if (prefix.contains("(")) { + String methodName = prefix; + int paramCount = 0; + if (prefix.contains("(")) { + int j = prefix.indexOf("("); + methodName = prefix.substring(0, j).trim(); + String params = prefix.substring(j + 1); + params = params.replace(")", "").trim(); + paramCount = countMethodParams(params); + } + + Iterator members = parent.getMemberIterator(); + while (members.hasNext()) { + Member m = members.next(); + if (m instanceof Method && m.getType() == null) { + Method method = (Method) m; + if (method.getName().equals(methodName) && method.getParameterCount() == paramCount) { + memberClickedListener.gotoMethod(method); + return; + } + } + } + } + parent = parent.getParentType(); + } + // check if clicked member is a method or field somewhere in the current class + String methodName = prefix; + int paramCount = 0; + if (prefix.contains("(")) { + int j = prefix.indexOf("("); + methodName = prefix.substring(0, j).trim(); + String params = prefix.substring(j + 1); + params = params.replace(")", "").trim(); + paramCount = countMethodParams(params); + } + + // since we did not have a . this is a method local var, a method parameter or a class field + // we check first the methods for local vars & parameters (we do not have the this. before the name, so this is the proper + // resolution order) + + Iterator methodIterator = tmpTd.getMethodIterator(); + while (methodIterator.hasNext()) { + Method method = methodIterator.next(); + if (methodName.equals(method.getName()) && method.getParameterCount() == paramCount) { + memberClickedListener.gotoMethod(method); + return; + } + else if (method.getBodyContainsOffset(offs)) { + // clicked variable is somewhere inside a method block, check if it is a local variable + for (int k = 0;k < method.getBody().getLocalVarCount();k++) { + if (prefix.equals(method.getBody().getLocalVar(k).getName())) { + memberClickedListener.gotoLocalVar(method.getBody().getLocalVar(k)); + return; + } + } + // check if the clicked variable is a method parameter + for (int k = 0;k < method.getParameterCount();k++) { + if (prefix.equals(method.getParameter(k).getName())) { + memberClickedListener.gotoMethodParameter(method.getParameter(k)); + return; + } + } + // what if it is in a child code block inside this method + // go from the deepest code block containing the cursor and search up the + // codeblock hierarchy + CodeBlock codeBlock = method.getBody().getDeepestCodeBlockContaining(offs); + while (codeBlock != null) { + for (int k = 0;k < codeBlock.getLocalVarCount();k++) { + if (prefix.equals(codeBlock.getLocalVar(k).getName())) { + memberClickedListener.gotoLocalVar(codeBlock.getLocalVar(k)); + return; + } + } + codeBlock = codeBlock.getParent(); + } + } + } + + // not found in the methods, check if this is a class field of this class + Iterator fieldIterator = tmpTd.getFieldIterator(); + while (fieldIterator.hasNext()) { + Field field = fieldIterator.next(); + if (prefix.equals(field.getName())) { + memberClickedListener.gotoField(field); + return; + } + } + // if we did not find it, and this TypeDeclaration (inner class) is static, we cannot check the parent TypeDeclarations + if (tmpTd.isStatic()) break; + } while ((tmpTd = tmpTd.getParentType()) != null); + // System.out.println("found matched imports not found"); + } + // System.out.println("not found " + prefix); + } /** * Loads completions for the text at the current caret position, if there @@ -828,67 +2955,79 @@ private void loadCompletionsForCaretPositionQualified(CompilationUnit cu, String alreadyEntered, Set retVal, TypeDeclaration td, Method currentMethod, String prefix, int offs) { - // TODO: Remove this restriction. - int dot = prefix.indexOf('.'); - if (dot>-1) { - System.out.println("[DEBUG]: Qualified non-this completions currently only go 1 level deep"); + if (EMPTY_STRING.equals(prefix)) { + // System.out.println("string is empty"); return; } - // TODO: Remove this restriction. - else if (!prefix.matches("[A-Za-z_][A-Za-z0-9_\\$]*")) { - System.out.println("[DEBUG]: Only identifier non-this completions are currently supported"); - return; - } +// TODO: Remove this restriction. +// else if (!prefix.matches("[A-Za-z_][A-Za-z0-9_\\$]*")) { +// System.out.println("[DEBUG]: Only identifier non-this completions are currently supported"); +// return; +// } String pkg = cu.getPackageName(); boolean matched = false; - - for (Iterator j=td.getMemberIterator(); j.hasNext(); ) { - - Member m = j.next(); - - // The prefix might be a field in the local class. - if (m instanceof Field) { - - Field field = (Field)m; - - if (field.getName().equals(prefix)) { - //System.out.println("FOUND: " + prefix + " (" + pkg + ")"); - Type type = field.getType(); - if (type.isArray()) { - ClassFile cf = getClassFileFor(cu, "java.lang.Object"); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); - FieldCompletion fc = FieldCompletion. - createLengthCompletion(this, type); - retVal.add(fc); - } - else if (!type.isBasicType()) { - String typeStr = type.getName(true, false); - ClassFile cf = getClassFileFor(cu, typeStr); - // Add completions for extended class type chain - if (cf!=null) { - Map typeParamMap = createTypeParamMap(type, cf); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); - // Add completions for all implemented interfaces - // TODO: Only do this if type is abstract! - for (int i=0; i typeParamMap = createTypeParamMap(type, cf); + addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap, cf.getClassName(false).equals(prefix)); + // Add completions for all implemented interfaces + // TODO: Only do this if type is abstract! +// for (int i=0; i imports = cu.getImports(); - List matches = jarManager.getClassesWithUnqualifiedName( - prefix, imports); - if (matches!=null) { - for (int i=0; i imports = cu.getImports(); + List matches = jarManager.getClassesWithUnqualifiedName( + prefix, imports); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + addCompletionsForStaticMembers(retVal, cu, cf, pkg); + } + } + } } } @@ -975,6 +3128,40 @@ else if (child.getNameStartOffset()>offs) { } + /** + * Loads methods, fields for current class if user enters ClassName.this. + * @param set + * @param cu + * @param td + */ + private void loadCompletionForClass(Set set, CompilationUnit cu, TypeDeclaration td) { + Iterator memberIterator = td.getMemberIterator(); + while (memberIterator.hasNext()) { + Member member = memberIterator.next(); + if (!member.isStatic()) { + if (member instanceof Method && ((Method) member).isConstructor()) continue; + set.add(member instanceof Method ? new MethodCompletion(this, (Method) member) : new FieldCompletion(this, (Field) member)); + } + } + } + + /** + * Loads the static members of a class when the user enters the Classname + * @param set + * @param cu + * @param td + */ + private void loadStaticCompletionForClass(Set set, CompilationUnit cu, TypeDeclaration td) { + // add static methods to completion + Iterator memberIterator = td.getMemberIterator(); + while (memberIterator.hasNext()) { + Member member = memberIterator.next(); + if (member.isStatic()) { + set.add(member instanceof Method ? new MethodCompletion(this, (Method) member) : new FieldCompletion(this, (Field) member)); + } + } + } + /** * Loads completions for a single import statement. @@ -991,6 +3178,9 @@ private void loadCompletionsForImport(Set set, List classes= jarManager.getClassesInPackage(pkg, inPkg); for (ClassFile cf : classes) { set.add(new ClassCompletion(this, cf)); + if (loadConstructors) { + addConstructors(set, cf, false); + } } } @@ -998,6 +3188,10 @@ private void loadCompletionsForImport(Set set, ClassFile cf = jarManager.getClassEntry(importStr); if (cf!=null) { set.add(new ClassCompletion(this, cf)); + // find constructors && add constructor completions as MethodCompletion + if (loadConstructors) { + addConstructors(set, cf, false); + } } } @@ -1068,5 +3262,186 @@ void setJavaProvider(JavaCompletionProvider javaProvider) { this.javaProvider = javaProvider; } - + /** + * Get the parameterized completions for a method body. This will determine if the caret is currently in a method + * call, and get the metod type with parameters, and returns exactly one MethodCompletion. This is mainly used + * to show the tooltip for the method parameters when pressing Ctrl-P + * + * @param cu + * @param tc + * @return + */ + public List getParameterizedCompletions(CompilationUnit cu, JTextComponent tc) { + List tokens = getTokenListForLine(tc); + org.fife.rsta.ac.java.rjc.lexer.Token token; + + int paramCounter = 0; + int parenCounter = 0; + int newIndex = -1; + for (int i = tokens.size() - 1;i >= 0;i--) { + token = tokens.get(i); + // increase the paramCounter only, if we are in the main method call, and not in some submethod call. + // so if parenCounter equals 0, means we are in the main method, if it is greater than 0, we encountered some + // other method call inside the method call + if (token.getType() == TokenTypes.SEPARATOR_COMMA && parenCounter == 0) paramCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_RPAREN) parenCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter > 0) parenCounter--; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter == 0) { + newIndex = i - 1; + break; + } + } + + if (newIndex == -1) return null; + // found method call start, now we need to find the method, and check the paramCounter'th parameter of it + // we could have multiple methods with similar signature (eg one parameter, or two parameters), + // in this case we load completion for all methods. Now we need to find out where our method declaration + // starts. + int startIndex = 0; + parenCounter = 0; + int endIndex = newIndex; + for (int i = newIndex;i >= 0;i--) { + token = tokens.get(i); + // if we encounter equals, comma or new keyword, or a ( without a ) we stop + if ((token.getType() & TokenTypes.OPERATOR) > 0 || (token.getType() & TokenTypes.ASSIGNMENT_OPERATOR) > 0 || token.getType() == TokenTypes.OPERATOR_LOGICAL_NOT || token.getType() == TokenTypes.KEYWORD_NEW || token.getType() == TokenTypes.SEPARATOR_COMMA) { + startIndex = i + 1; + break; + } + else if (token.getType() == TokenTypes.SEPARATOR_RPAREN) parenCounter++; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter > 0) parenCounter--; + else if (token.getType() == TokenTypes.SEPARATOR_LPAREN && parenCounter == 0) { + startIndex = i + 1; + break; + } + } + + if (startIndex <= endIndex) { + // now the first token at startIndex should be the variable name. If startindex == endindex, this + // should already be a method call + if (startIndex == endIndex) { + // search for a method in the current CU with matching signature + // if the token is super and we are in a constructor, load super class constructors + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(tc.getCaretPosition()); + + if (SUPER.equals(tokens.get(startIndex).getLexeme())) { + Method m = findCurrentMethod(td, tc.getCaretPosition()); + if (m != null && m.getType() == null && m.getParentTypeDeclaration() instanceof NormalClassDeclaration) { + NormalClassDeclaration parentType = (NormalClassDeclaration) m.getParentTypeDeclaration(); + List methods = findMethodsForType(cu, parentType.getExtendedType(), parentType.getExtendedType().getName(false, false), paramCounter + 1); + List result = new ArrayList(); + for (Object obj : methods) { + ParameterizedCompletion c; + if (obj instanceof Method) c = new MethodCompletion(this, (Method) obj); + else c = new MethodCompletion(this, (MethodInfo) obj); + // check if the completion is not already on the list + if (!result.contains(c)) result.add(c); + } + return result; + } + } + + while (td != null) { + List methods = findMethodsForType(cu, new Type(td.getName(true)), tokens.get(startIndex).getLexeme(), paramCounter + 1); + if (methods != null && methods.size() > 0) { + List result = new ArrayList(); + for (Object obj : methods) { + ParameterizedCompletion c; + if (obj instanceof Method) c = new MethodCompletion(this, (Method) obj); + else c = new MethodCompletion(this, (MethodInfo) obj); + // check if the completion is not already on the list + if (!result.contains(c)) result.add(c); + } + return result; + } + td = td.getParentType(); + } + + // check for td's child types, use a helper method because of recursion + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + td = getTypeDeclarationForInnerType(cu.getTypeDeclaration(i), new Type(tokens.get(startIndex).getLexeme())); + if (td != null) { + List methods = findMethodsForType(cu, new Type(td.getName(true)), tokens.get(startIndex).getLexeme(), paramCounter + 1); + if (methods != null && methods.size() > 0) { + List result = new ArrayList(); + for (Object obj : methods) { + ParameterizedCompletion c; + if (obj instanceof Method) c = new MethodCompletion(this, (Method) obj); + else c = new MethodCompletion(this, (MethodInfo) obj); + // check if the completion is not already on the list + if (!result.contains(c)) result.add(c); + } + return result; + } + } + } + + // may be this is a class constructor not in the current context + String fqDnName = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jarManager, tokens.get(startIndex).getLexeme()); + ClassFile cf = jarManager.getClassEntry(fqDnName); + if (cf != null) { + List methods = findMethods(cf, tokens.get(startIndex).getLexeme(), paramCounter + 1); + if (methods != null && methods.size() > 0) { + List result = new ArrayList(); + for (MethodInfo methodInfo : methods) { + ParameterizedCompletion c = new MethodCompletion(this, methodInfo); + // check if the completion is not already on the list + if (!result.contains(c)) result.add(c); + } + return result; + } + } + } + else { + token = tokens.get(startIndex); + String varName = token.getLexeme(); + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(tc.getCaretPosition()); + if (td != null) { + // have a variable type, we should now follow the method calls till we reach to the end + List methods = new ArrayList(); + StringBuilder sb = new StringBuilder(); + for (int i = startIndex + 2;i <= endIndex;i++) { + token = tokens.get(i); + // skip operators like <> for generics + if (token.getType() != TokenTypes.SEPARATOR_DOT && (token.getType() & TokenTypes.OPERATOR) == 0) { + sb.append(token.getLexeme()); + } + else if ((token.getType() & TokenTypes.OPERATOR) == 0) { + methods.add(sb.toString()); + sb = new StringBuilder(); + } + } + // this should be the last part + if (sb.length() > 0) { + methods.add(sb.toString()); + } + + // build a method call except the last call + sb = new StringBuilder(varName); + for (int i = 0;i < methods.size()-1;i++) { + sb.append('.'); + sb.append(methods.get(i)); + } + + Type type = resolveType2(cu, sb.toString(), td, findCurrentMethod(td, tc.getCaretPosition()), sb.toString(), tc.getCaretPosition()); + if (type != null) { + // get the possible parameter types for the given method. if methods size is 0, try with varname (possible constructor?) + List methodList = findMethodsForType(cu, type, methods.size() == 0 ? varName : methods.get(methods.size() - 1), paramCounter + 1); + if (methodList != null && methodList.size() > 0) { + List result = new ArrayList(); + for (Object obj : methodList) { + ParameterizedCompletion c; + if (obj instanceof Method) c = new MethodCompletion(this, (Method) obj); + else c = new MethodCompletion(this, (MethodInfo) obj); + // check if the completion is not already on the list + if (!result.contains(c)) result.add(c); + } + return result; + } + } + } + } + } + + return null; + } } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/java/SourceParamChoicesProvider.java b/src/main/java/org/fife/rsta/ac/java/SourceParamChoicesProvider.java index f4933400..bdd5229f 100644 --- a/src/main/java/org/fife/rsta/ac/java/SourceParamChoicesProvider.java +++ b/src/main/java/org/fife/rsta/ac/java/SourceParamChoicesProvider.java @@ -20,17 +20,11 @@ import org.fife.rsta.ac.LanguageSupport; import org.fife.rsta.ac.LanguageSupportFactory; -import org.fife.rsta.ac.java.rjc.ast.CodeBlock; -import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.Field; -import org.fife.rsta.ac.java.rjc.ast.FormalParameter; -import org.fife.rsta.ac.java.rjc.ast.LocalVariable; -import org.fife.rsta.ac.java.rjc.ast.Member; -import org.fife.rsta.ac.java.rjc.ast.Method; -import org.fife.rsta.ac.java.rjc.ast.NormalClassDeclaration; -import org.fife.rsta.ac.java.rjc.ast.NormalInterfaceDeclaration; +import org.fife.rsta.ac.java.classreader.ClassFile; +import org.fife.rsta.ac.java.classreader.FieldInfo; +import org.fife.rsta.ac.java.classreader.MethodInfo; +import org.fife.rsta.ac.java.rjc.ast.*; import org.fife.rsta.ac.java.rjc.ast.Package; -import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; import org.fife.rsta.ac.java.rjc.lang.Type; import org.fife.ui.autocomplete.BasicCompletion; import org.fife.ui.autocomplete.Completion; @@ -67,13 +61,59 @@ class SourceParamChoicesProvider implements ParameterChoicesProvider { * @param pkg * @param list */ - private void addPublicAndProtectedFieldsAndGetters(Type type, JarManager jm, + private void addPublicAndProtectedFieldsAndGetters(CompilationUnit cu, Type type, String paramType, JarManager jm, Package pkg, List list) { // TODO: Implement me. - + ClassFile cf = jm.getClassEntry(type.getName(true)); + if (cf != null) { + // do processing according to cf, search for public methods and fields + for (int i = 0;i < cf.getMethodCount();i++) { + MethodInfo mi = cf.getMethodInfo(i); + if (isTypeCompatible(null, new Type(findFullyQualifiedNameFor(cu, jm, mi.getReturnTypeString(true))), paramType, jm)) { + MethodCompletion mc = new MethodCompletion(((JavaCompletionProvider) provider).getDefaultCompletionProvider(), mi); + if (!list.contains(mc)) list.add(mc); + } + } + + for (int i = 0;i < cf.getFieldCount();i++) { + FieldInfo fi = cf.getFieldInfo(i); + if (isTypeCompatible(null, new Type(findFullyQualifiedNameFor(cu, jm, fi.getTypeString(true))), paramType, jm)) { + FieldCompletion fc = new FieldCompletion(((JavaCompletionProvider) provider).getDefaultCompletionProvider(), fi); + if (!list.contains(fc)) list.add(fc); + } + } + } } + private void addPublicAndProtectedFieldsAndGetters(CompilationUnit cu, TypeDeclaration typeDeclaration, String type, JarManager jm, + Package pkg, List list) { + + for (Iterator i=typeDeclaration.getMemberIterator(); i.hasNext(); ) { + + Member member = i.next(); + + if (member instanceof Field) { + Type fieldType = member.getType(); + if (isTypeCompatible(cu, new Type(findFullyQualifiedNameFor(cu, jm, fieldType.getName(true))), type, jm)) { + //members.add(member.getName()); + FieldCompletion fc = new FieldCompletion(((JavaCompletionProvider) provider).getDefaultCompletionProvider(), (Field)member); + if (!list.contains(fc)) list.add(fc); + } + } + else { // Method + Method method = (Method)member; + if (isSimpleGetter(method)) { + if (isTypeCompatible(cu, new Type(findFullyQualifiedNameFor(cu, jm, method.getType().getName(true))), type, jm)) { + //members.add(member.getName() + "()"); + MethodCompletion mc = new MethodCompletion(((JavaCompletionProvider) provider).getDefaultCompletionProvider(), method); + if (!list.contains(mc)) list.add(mc); + } + } + } + } + } + /** * Gets all local variables, fields, and simple getters defined in a @@ -86,13 +126,14 @@ private void addPublicAndProtectedFieldsAndGetters(Type type, JarManager jm, * @param offs The offset of the caret. * @return The list of stuff, or an empty list if none are found. */ - public List getLocalVarsFieldsAndGetters( - NormalClassDeclaration ncd, String type, int offs) { + public void getLocalVarsFieldsAndGetters(CompilationUnit cu, + NormalClassDeclaration ncd, String type, int offs, List members, JarManager jm) { - List members = new ArrayList(); +// List members = new ArrayList(); if (!ncd.getBodyContainsOffset(offs)) { - return members; +// return members; + return; } // First, if the offset is in a method, get any local variables in @@ -104,10 +145,11 @@ public List getLocalVarsFieldsAndGetters( Iterator i = method.getParameterIterator(); while (i.hasNext()) { FormalParameter param = i.next(); - Type paramType = param.getType(); - if (isTypeCompatible(paramType, type)) { + Type paramType = new Type(findFullyQualifiedNameFor(cu, jm, param.getType().getName(true))); + if (isTypeCompatible(cu, paramType, type, jm)) { //members.add(param.getName()); - members.add(new LocalVariableCompletion(provider, param)); + LocalVariableCompletion lvc = new LocalVariableCompletion(provider, param); + if (!members.contains(lvc)) members.add(lvc); } } @@ -115,16 +157,20 @@ public List getLocalVarsFieldsAndGetters( CodeBlock body = method.getBody(); if (body!=null) { // Should always be true? CodeBlock block = body.getDeepestCodeBlockContaining(offs); - List vars = block.getLocalVarsBefore(offs); - for (LocalVariable var : vars) { - Type varType = var.getType(); - if (isTypeCompatible(varType, type)) { - //members.add(var.getName()); - members.add(new LocalVariableCompletion(provider, var)); - } - } + // we should go upwards till the method body from the deepest codeblock + do { + List vars = block.getLocalVarsBefore(offs); + for (LocalVariable var : vars) { + Type varType = new Type(findFullyQualifiedNameFor(cu, jm, var.getType().getName(true))); + if (isTypeCompatible(cu, varType, type, jm)) { + //members.add(var.getName()); + LocalVariableCompletion lvc = new LocalVariableCompletion(provider, var); + if (!members.contains(lvc)) members.add(lvc); + } + } + block = block.getParent(); + } while (block != null); } - } // Next, any fields/getters taking no parameters (for simplicity) @@ -134,28 +180,165 @@ public List getLocalVarsFieldsAndGetters( Member member = i.next(); if (member instanceof Field) { - Type fieldType = member.getType(); - if (isTypeCompatible(fieldType, type)) { + Type fieldType = new Type(findFullyQualifiedNameFor(cu, jm, member.getType().getName(true))); + if (isTypeCompatible(cu, fieldType, type, jm)) { //members.add(member.getName()); - members.add(new FieldCompletion(provider, (Field)member)); + FieldCompletion fc = new FieldCompletion(((JavaCompletionProvider) provider).getDefaultCompletionProvider(), (Field)member); + if (!members.contains(fc)) members.add(fc); } } else { // Method method = (Method)member; if (isSimpleGetter(method)) { - if (isTypeCompatible(method.getType(), type)) { + if (isTypeCompatible(cu, new Type(findFullyQualifiedNameFor(cu, jm, method.getType().getName(true))), type, jm)) { //members.add(member.getName() + "()"); - members.add(new MethodCompletion(provider, method)); + MethodCompletion mc = new MethodCompletion(((JavaCompletionProvider) provider).getDefaultCompletionProvider(), method); + if (!members.contains(mc)) members.add(mc); } } } } - return members; +// return members; } + protected void addParamChoices(CompilationUnit cu, List list, TypeDeclaration typeDec, ParameterizedCompletion.Parameter param, int dot, JarManager jm) { + Package pkg = typeDec.getPackage(); + + String paramFQType = findFullyQualifiedNameFor(cu, jm, param.getType()); + + // If we're in a class, we'll have to check for local variables, etc. + if (typeDec instanceof NormalClassDeclaration) { + + // Get accessible members of this type. + NormalClassDeclaration ncd = (NormalClassDeclaration)typeDec; + getLocalVarsFieldsAndGetters(cu, ncd, paramFQType, dot, list, jm); +// list = typeDec.getAccessibleMembersOfType(param.getType(), dot); + + // Get accessible members of the extended type. + Type extended = ncd.getExtendedType(); + if (extended!=null) { + addPublicAndProtectedFieldsAndGetters(cu, extended, paramFQType, jm, pkg, list); + } + + // Get accessible members of any implemented interfaces. + for (Iterator i=ncd.getImplementedIterator(); i.hasNext(); ) { + Type implemented = i.next(); + addPublicAndProtectedFieldsAndGetters(cu, implemented, paramFQType, jm, pkg, list); + } + + + } + + // If we're an interface, local vars, etc. don't exist + else if (typeDec instanceof NormalInterfaceDeclaration) { + // Nothing to do + } + + // If we're in an enum... +// else if (typeDec instanceof EnumDeclaration) { +// // TODO: Implement me +// } + + // Check for any public/protected fields/getters in enclosing type. + if (!typeDec.isStatic()) { + // add parent type declaration + TypeDeclaration enclosing = typeDec.getParentType(); + while (enclosing != null && !enclosing.isStatic()) { + addPublicAndProtectedFieldsAndGetters(cu, enclosing, paramFQType, jm, pkg, list); + enclosing = enclosing.getParentType(); + } + } + + } + + /** + * This method does try to look up the given type in the import section. It checks first if it is a primitive type + * if so, returns it. If not a primitive type and does not contain dots (eg definition is not like java.io.Serializable), + * checks the import section for matching import statement. If found, returns the import. If still not found, then + * either it is a java.lang class, or is in a * import statement (eg. import java.io.*) For starred imports a matching + * class is checked against all classes in the given import wildcard. + * @param cu + * @param type + * @return + */ + public static String findFullyQualifiedNameFor(CompilationUnit cu, JarManager jm, String type) { + if (jm == null || type == null) return null; + // if the type already contains a dot, consider fully qualified + if (type.startsWith("java.lang.")) return type; + // if the type is a primitive type, return + if (isPrimitiveNumericType(type) || type.equals("boolean")) return type; + // check for imports + if (cu != null) { + for (ImportDeclaration importDeclaration : cu.getImports()) { + if (importDeclaration.isWildcard()) { + // handle wildcard import, get the class list in the given package + String pkg = importDeclaration.getName().substring(0, importDeclaration.getName().lastIndexOf(".")); + List classFiles = jm.getClassesInPackage(pkg, false); + if (classFiles != null) { + for (ClassFile cf : classFiles) { + if (cf.getClassName(false).equals(type)) { + return cf.getClassName(true); + } + } + } + } + else { + if (importDeclaration.getName().endsWith(type)) { + return importDeclaration.getName(); + } + } + } + // check if it is a class in the current cu + if (cu.getTypeDeclarationCount() > 0) { + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + TypeDeclaration td = cu.getTypeDeclaration(i); + String t = findTypeInTypeDeclaration(td, type); + if (t != null) return t; + } + } + } + +// check for type if it contains a . and try to find it as inner class + if (type.contains(".")) { + // check if we can find it as a classFile + ClassFile cf = jm.getClassEntry(type); + if (cf != null) { + return cf.getClassName(true); + } + // if not found, try to find it as inner class + String className = type.substring(0, type.indexOf(".")); + String innerClassName = type.substring(type.indexOf(".") + 1); + String fqdn = findFullyQualifiedNameFor(cu, jm, className); + cf = jm.getClassEntry(fqdn + "$" + innerClassName); + if (cf != null) { + return fqdn + "$" + innerClassName; + } + } + + // not found might be a java.lang class + if ("void".equals(type)) { + return type; + } + return "java.lang." + type; + } + + public static String findTypeInTypeDeclaration(TypeDeclaration td, String type) { + if (td.getName(false).equals(type) || td.getName(true).equals(type)) { + return td.getName(true); + } + if (td.getChildTypeCount() > 0) { + for (int i = 0;i < td.getChildTypeCount();i++) { + TypeDeclaration childTd = td.getChildType(i); + String t = findTypeInTypeDeclaration(childTd, type); + if (t != null) return t; + } + } + return null; + } + /** * {@inheritDoc} @@ -188,47 +371,10 @@ public List getParameterChoices(JTextComponent tc, return null; } - List list = null; - Package pkg = typeDec.getPackage(); - provider = jls.getCompletionProvider(textArea); - - // If we're in a class, we'll have to check for local variables, etc. - if (typeDec instanceof NormalClassDeclaration) { - - // Get accessible members of this type. - NormalClassDeclaration ncd = (NormalClassDeclaration)typeDec; - list = getLocalVarsFieldsAndGetters(ncd, param.getType(), dot); -// list = typeDec.getAccessibleMembersOfType(param.getType(), dot); - - // Get accessible members of the extended type. - Type extended = ncd.getExtendedType(); - if (extended!=null) { - addPublicAndProtectedFieldsAndGetters(extended, jm, pkg, list); - } + List list = new ArrayList(); + provider = jls.getCompletionProvider(textArea); - // Get accessible members of any implemented interfaces. - for (Iterator i=ncd.getImplementedIterator(); i.hasNext(); ) { - Type implemented = i.next(); - addPublicAndProtectedFieldsAndGetters(implemented,jm,pkg,list); - } - - - } - - // If we're an interface, local vars, etc. don't exist - else if (typeDec instanceof NormalInterfaceDeclaration) { - // Nothing to do - } - - // If we're in an enum... - else {//if (typeDec instanceof EnumDeclaration) { - // TODO: Implement me - } - - // Check for any public/protected fields/getters in enclosing type. - if (!typeDec.isStatic()) { - // TODO: Implement me. - } + addParamChoices(cu, list, typeDec, param, dot, jm); // Add defaults for common types - "0" for primitive numeric types, // "null" for Objects, etc. @@ -237,7 +383,7 @@ else if (typeDec instanceof NormalInterfaceDeclaration) { if (typeObj instanceof Type) { Type type = (Type)typeObj; if (type.isBasicType()) { - if (isPrimitiveNumericType(type)) { + if (isPrimitiveNumericType(type.getName(true))) { list.add(new SimpleCompletion(provider, "0")); } else { // is a "boolean" type @@ -256,8 +402,8 @@ else if (typeDec instanceof NormalInterfaceDeclaration) { } - private boolean isPrimitiveNumericType(Type type) { - String str = type.getName(true); + public static boolean isPrimitiveNumericType(String str) { +// String str = type.getName(true); return "byte".equals(str) || "float".equals(str) || "double".equals(str) || "int".equals(str) || "short".equals(str) || "long".equals(str); @@ -280,34 +426,202 @@ private boolean isSimpleGetter(Method method) { * Returns whether a Type and a type name are type * compatible. This method currently is a sham! * - * @param type - * @param typeName + * @param type the type to assign variable to + * @param typeName the type of the variable * @return */ // TODO: Get me working! Probably need better parameters passed in!!! - private boolean isTypeCompatible(Type type, String typeName) { - - String typeName2 = type.getName(false); + // changed to public static, since this logic is also used in SourceCompletionProvider. Consider move these methods to + // Util + public static boolean isTypeCompatible(CompilationUnit cu, Type type, String typeName, JarManager jm) { - // Remove generics info for now - // TODO: Handle messy generics cases - int lt = typeName2.indexOf('<'); - if (lt>-1) { - String arrayDepth = null; - int brackets = typeName2.indexOf('[', lt); - if (brackets>-1) { - arrayDepth = typeName2.substring(brackets); - } - typeName2 = typeName2.substring(lt); - if (arrayDepth!=null) { - typeName2 += arrayDepth; - } - } + String typeName2 = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jm, type.getName(true)); + typeName = SourceParamChoicesProvider.findFullyQualifiedNameFor(cu, jm, typeName); - return typeName2.equalsIgnoreCase(typeName); + // void type cannot accept anything + if ("void".equals(typeName2)) return false; + // Remove generics info for now + // TODO: Handle messy generics cases +// int lt = typeName2.indexOf('<'); +// if (lt>-1) { +// String arrayDepth = null; +// int brackets = typeName2.indexOf('[', lt); +// if (brackets>-1) { +// arrayDepth = typeName2.substring(brackets); +// } +// typeName2 = typeName2.substring(lt); +// if (arrayDepth!=null) { +// typeName2 += arrayDepth; +// } +// } + + // if typeName is Object, accept all non primitive types + if (typeName.equals(Object.class.getName()) && !isPrimitiveNumericType(type.getName(true)) && !typeName2.equals("boolean")) { + return true; + } + if (typeName2.equalsIgnoreCase(typeName)) { + return true; + } + if (isPrimitiveNumericType(type.getName(true)) || typeName2.equals("boolean")) { + // if the type is primitive, we accept assignment from object types + if (matchPrimitiveWithObject(type.getName(true), typeName)) { + return true; + } + } + if (isPrimitiveNumericType(typeName) || typeName.equals("boolean")) { + // if the variable type is primitive, check if we can accept assignment to an object type + if (matchPrimitiveWithObject(typeName, type.getName(true))) { + return true; + } + } + + // otherwise we should check if the parameter type (typeName) is assignable from the variable type (typeName2) + boolean result = checkTypeInClassFiles(jm, typeName2, typeName); + if (result) return true; + + if (cu != null && cu.getTypeDeclarationCount() > 0) { + for (int i = 0; i < cu.getTypeDeclarationCount(); i++) { + TypeDeclaration td = cu.getTypeDeclaration(i); + result = checkTypeInTypeDeclaration(cu, td, jm, typeName2, typeName); + if (result) return true; + } + } + + return false; } + /** + * This method matches primitive types with corresponding object types + * + * @param primitiveType the primitive type to check + * @param objectType the object type to check + * @return true if the primitive type can be assigned from the object type, false otherwise + */ + public static boolean matchPrimitiveWithObject(String primitiveType, String objectType) + { + if (primitiveType.equals("int") && objectType.equals(Integer.class.getName())) return true; + if (primitiveType.equals("long") && objectType.equals(Long.class.getName())) return true; + if (primitiveType.equals("double") && objectType.equals(Double.class.getName())) return true; + if (primitiveType.equals("float") && objectType.equals(Float.class.getName())) return true; + if (primitiveType.equals("byte") && objectType.equals(Byte.class.getName())) return true; + if (primitiveType.equals("short") && objectType.equals(Short.class.getName())) return true; + if (primitiveType.equals("boolean") && objectType.equals(Boolean.class.getName())) return true; + return false; + } + + /** + * This method checks if a variable type declared in the current compilation unit is assignable to the paramType + * + * @param cu + * @param td + * @param jm used to pass to the checkTypeInClassFiles method if a local class's superclass is outside this CU + * @param varType + * @param paramType + * @return + */ + public static boolean checkTypeInTypeDeclaration(CompilationUnit cu, TypeDeclaration td, JarManager jm, String varType, String paramType) { + // check in the local compilation unit if we find a matching type + if (td instanceof NormalClassDeclaration) { + NormalClassDeclaration ncd = (NormalClassDeclaration) td; + if (ncd.getName(true).equals(varType)) { + // found matching typeDeclaration + // check if superclass or implemented interfaces do match the paramType + String fqSuperclass = findFullyQualifiedNameFor(cu, jm, ncd.getExtendedType().getName(true)); + if (paramType.equals(fqSuperclass)) return true; + // otherwise we should search for superclass's superclass, etc. check first the class files + boolean result = checkTypeInClassFiles(jm, fqSuperclass, paramType); + if (result) return true; + + // not found in superclass, check the interfaces + if (ncd.getImplementedCount() > 0) { + Iterator implIterator = ncd.getImplementedIterator(); + while (implIterator.hasNext()) { + Type implType = implIterator.next(); + String fqInterface = findFullyQualifiedNameFor(cu, jm, implType.getName(true)); + // if the paramType matches the interface, we accept + if (fqInterface.equals(paramType)) return true; + // otherwise check if the interface class file has some extending interfaces which might match + result = checkTypeInClassFiles(jm, fqInterface, paramType); + if (result) return true; + // check if this interface is defined inside the cu + for (int i = 0;i < cu.getTypeDeclarationCount();i++) { + result = checkTypeInTypeDeclaration(cu, cu.getTypeDeclaration(i), jm, fqInterface, paramType); + if (result) return true; + } + } + } + } + // check child td's + if (ncd.getChildTypeCount() > 0) { + for (int i = 0;i < ncd.getChildTypeCount();i++) { + boolean result = checkTypeInTypeDeclaration(cu, ncd.getChildType(i), jm, varType, paramType); + if (result) return true; + } + } + } + // if interface declaration + else if (td instanceof NormalInterfaceDeclaration) { + NormalInterfaceDeclaration nitd = (NormalInterfaceDeclaration) td; + if (nitd.getName(true).equals(varType)) { + // found if declaration for variable, check the extending classes + Iterator extTypes = nitd.getExtendedIterator(); + while (extTypes.hasNext()) { + Type t = extTypes.next(); + String typeName = findFullyQualifiedNameFor(cu, jm, t.getName(true)); + if (typeName.equals(paramType)) return true; + // check extended interfaces + boolean result = checkTypeInClassFiles(jm, typeName, paramType); + if (result) return true; + } + } + } + + return false; + } + + /** + * This method checks the given varType if it is assignable to the given paramType. + * + * @param jm + * @param varType + * @param paramType + * @return + */ + public static boolean checkTypeInClassFiles(JarManager jm, String varType, String paramType) { + ClassFile variableTypeCf = jm.getClassEntry(varType); + + if (variableTypeCf != null) { + // check if paramType (parameter type) is a super class of variableType + if (paramType.equals(variableTypeCf.getSuperClassName(false))) return true; + // if not, check for superclass's superclass, etc. till Object. If we reach object, the variableType cannot be + // assigned to the parameter + ClassFile cf; + if (variableTypeCf.getSuperClassName(true) == null) cf = null; + else cf = jm.getClassEntry(variableTypeCf.getSuperClassName(true)); + while (cf != null && !cf.getClassName(true).equals(Object.class.getName())) { + if (paramType.equals(cf.getClassName(false))) return true; + cf = jm.getClassEntry(cf.getSuperClassName(true)); + } + + // if not found in the chain of superclass hierarchy, check the interfaces + for (int i = 0;i < variableTypeCf.getImplementedInterfaceCount();i++) { +// String ifName = variableTypeCf.getImplementedInterfaceName(i, true); +// if (paramType.equals(ifName)) return true; + // if not found in this interface, check if this interface extends the typeName + cf = jm.getClassEntry(variableTypeCf.getImplementedInterfaceName(i, true)); + // interfaces can only have superclasses (super interfaces) + while (cf != null) { + if (paramType.equals(cf.getClassName(true))) return true; + if (cf.getSuperClassName(true) == null) cf = null; + else cf = jm.getClassEntry(cf.getSuperClassName(true)); + } + } + } + + return false; + } + /** * A very simple, low-relevance parameter choice completion. This is diff --git a/src/main/java/org/fife/rsta/ac/java/SuperConstructorData.java b/src/main/java/org/fife/rsta/ac/java/SuperConstructorData.java new file mode 100644 index 00000000..6876772e --- /dev/null +++ b/src/main/java/org/fife/rsta/ac/java/SuperConstructorData.java @@ -0,0 +1,105 @@ +package org.fife.rsta.ac.java; + +import org.fife.rsta.ac.java.rjc.lang.Type; +import org.fife.ui.autocomplete.ParameterizedCompletion; + +import java.util.List; + +/** + * @author Zoltán Kuroli + * @since 2017.03.28. + */ +public class SuperConstructorData implements MemberCompletion.Data { + + private Type enclosingType; + private List parameters; + private String name; + + public SuperConstructorData(Type enclosingType, List parameters) + { + this.enclosingType = enclosingType; + this.parameters = parameters; + this.name = "super"; + } + + public SuperConstructorData(String name, Type enclosingType, List parameters) + { + this.enclosingType = enclosingType; + this.parameters = parameters; + this.name = name; + } + + @Override + public String getEnclosingClassName(boolean fullyQualified) + { + return enclosingType.getName(fullyQualified); + } + + @Override + public String getSignature() + { + StringBuilder sb = new StringBuilder(name); + sb.append('('); + int count = parameters.size(); + for (int i=0; i getMethodInfoByName(String name, int argCount) { return methods; } + /** + * Returns all method overloads with the specified name and number of + * arguments. It will return also methods, which have more arguments, than + * the desired number given in argCount. + * + * @param name The method name. + * @param argCount The number of arguments. If this is less than zero, + * all overloads will be returned, regardless of argument count. + * @return Any method overloads with the given name and minimal argument count, or + * null if none. This is a list of + * {@link MethodInfo}s. + * @see #getMethodInfoByName(String) + */ + public List getMethodInfoByNameAndMinimalArguments(String name, int argCount) { + List methods = null; + for (int i=0; i(1); // Usually just 1 + } + methods.add(info); + } + } + } + return methods; + } + /** * Returns the package for this class or interface. diff --git a/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java b/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java index 2e94841e..c64a99b7 100644 --- a/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java +++ b/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java @@ -71,6 +71,8 @@ public class MethodInfo extends MemberInfo implements AccessFlags { */ private String returnType; + private String returnTypeQualified; + /** * Cached string representing the name and parameters for this method. */ @@ -454,6 +456,15 @@ public String getReturnTypeString(boolean fullyQualified) { return returnType; } + public String getReturnTypeFull() { + if (returnTypeQualified == null) { + returnTypeQualified = getReturnTypeStringFromTypeSignature(true); + if (returnTypeQualified == null) { + returnTypeQualified = getReturnTypeStringFromDescriptor(true); + } + } + return returnTypeQualified; + } /** * Returns the return type of this method, as determined by a snippet diff --git a/src/main/java/org/fife/rsta/ac/java/rjc/lang/TypeParameter.java b/src/main/java/org/fife/rsta/ac/java/rjc/lang/TypeParameter.java index 76fc9e27..f88400c6 100644 --- a/src/main/java/org/fife/rsta/ac/java/rjc/lang/TypeParameter.java +++ b/src/main/java/org/fife/rsta/ac/java/rjc/lang/TypeParameter.java @@ -53,5 +53,8 @@ public String getName() { return name.getLexeme(); } - + public List getTypes() + { + return bounds; + } } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/java/rjc/lexer/Scanner.java b/src/main/java/org/fife/rsta/ac/java/rjc/lexer/Scanner.java index 36844821..3e6804a3 100644 --- a/src/main/java/org/fife/rsta/ac/java/rjc/lexer/Scanner.java +++ b/src/main/java/org/fife/rsta/ac/java/rjc/lexer/Scanner.java @@ -103,12 +103,12 @@ private void pushOntoStack(Token t) { if (t!=null && !stack.isEmpty() && t.equals(stack.peek())) { System.err.println("ERROR: Token being duplicated: " + t); Thread.dumpStack(); - System.exit(5); +// System.exit(5); } else if (t==null) { System.err.println("ERROR: null token pushed onto stack"); Thread.dumpStack(); - System.exit(6); +// System.exit(6); } stack.push(t); } diff --git a/src/main/java/org/fife/rsta/ac/java/rjc/parser/ASTFactory.java b/src/main/java/org/fife/rsta/ac/java/rjc/parser/ASTFactory.java index 55fab60e..9663d06f 100644 --- a/src/main/java/org/fife/rsta/ac/java/rjc/parser/ASTFactory.java +++ b/src/main/java/org/fife/rsta/ac/java/rjc/parser/ASTFactory.java @@ -261,8 +261,76 @@ private CodeBlock _getBlock(CompilationUnit cu, CodeBlock parent, case KEYWORD_FOR: // TODO: Get local var (e.g. "int i", "Iterator i", etc.) // Fall through + List localVariables = new ArrayList(); + int nextType = s.yyPeekCheckType(); + while (nextType!=-1 && nextType!=SEPARATOR_LPAREN) { + t = s.yylex(); // Grab the (unexpected) token + if (t!=null) { // Should always be true + ParserNotice pn = new ParserNotice(t, "Unexpected token"); + cu.addParserNotice(pn); + } + nextType = s.yyPeekCheckType(); + } + if (nextType==SEPARATOR_LPAREN) { + // extract local vars here + s.eatThroughNext(SEPARATOR_LPAREN); + int parenCounter = 1; + // we are interested in tokens until we encounter a '=' or a ':' + boolean varFinal = false; + String varType = null; + List variables = new ArrayList(); + while (s.yyPeekCheckType() != OPERATOR_EQUALS && s.yyPeekCheckType() != OPERATOR_COLON && s.yyPeekCheckType() != SEPARATOR_SEMICOLON) { + if (s.yyPeekCheckType() == KEYWORD_FINAL) { + varFinal = true; + s.yylex(); // eat final keyword + } + else if (varType == null) { + varType = s.yylex().getLexeme(); + } + else { + variables.add(s.yylex()); + } + } + + // if we had only one variable without type definition, means for (i=x;...) + // so we do not register any local variable. if we have variables and a varType + // the for was declared similar to this for (int i=0;....) or for (Iterator it=x.getIterator();..) or for (MyObject x : y) + if (variables.size() == 0 && varType != null) { + varType = null; + } + + if (varType != null) { + for (Token variable : variables) { + LocalVariable localVariable = new LocalVariable(s, varFinal, new Type(varType), variable.getOffset(), variable.getLexeme()); + localVariables.add(localVariable); + } + } + + // read till we hit the closing ) of the for, or encounter a { or } + while (parenCounter > 0) { + Token token = s.yylex(); + if (token.getType() == SEPARATOR_LPAREN) parenCounter++; + else if (token.getType() == SEPARATOR_RPAREN) parenCounter--; + else if (token.getType() == SEPARATOR_LBRACE || token.getType() == SEPARATOR_RBRACE) { + // encountered an opening or closing brace, stop cycle + s.yyPushback(token); + break; + } + } + } + nextType = s.yyPeekCheckType(); + if (nextType==SEPARATOR_LBRACE) { + child = _getBlock(cu, block, m, s, isStatic, depth+1); + // if we have any variable declared in the for () we add them here as local variables + if (localVariables.size() > 0) { + for (LocalVariable localVariable : localVariables) child.addLocalVariable(localVariable); + } + block.add(child); + atStatementStart = true; + } + break; case KEYWORD_WHILE: - int nextType = s.yyPeekCheckType(); + nextType = s.yyPeekCheckType(); while (nextType!=-1 && nextType!=SEPARATOR_LPAREN) { t = s.yylex(); // Grab the (unexpected) token if (t!=null) { // Should always be true @@ -431,8 +499,7 @@ private TypeDeclaration _getClassOrInterfaceDeclaration(CompilationUnit cu, throws IOException { log("Entering _getClassOrInterfaceDeclaration"); - Token t = s.yyPeekNonNull( - "class, enum, interface or @interface expected"); + Token t = s.yyPeekNonNull("class, enum, interface or @interface expected"); if (modList==null) { // Not yet read in modList = _getModifierList(cu, s); @@ -732,6 +799,11 @@ private void _getInterfaceBody(CompilationUnit cu, Scanner s, iDec.setBodyStartOffset(s.createOffset(t.getOffset())); t = s.yylexNonNull("InterfaceBody expected"); + // if the next token is the closing brace (empty interface body), register the offset, otherwise + // the end offset will be null, and will provide various errors + if (t.getType() == SEPARATOR_RBRACE) { + iDec.setBodyEndOffset(s.createOffset(t.getOffset())); + } while (t.getType() != SEPARATOR_RBRACE) { diff --git a/src/main/resources/examples/JavaExample.txt b/src/main/resources/examples/JavaExample.txt index 57adb381..79356ed9 100644 --- a/src/main/resources/examples/JavaExample.txt +++ b/src/main/resources/examples/JavaExample.txt @@ -1,57 +1,140 @@ +package rsta.example; + +import java.util.HashMap; +import java.io.Serializable; +import java.util.List; +import java.util.ArrayList; +import java.awt.Component; +import javax.swing.JTextField; +import javax.swing.JButton; + public class ExampleCode { - private String strField; - protected int aaa; - protected int abb; - public float foo; + public static final String ASDF = "ASDF"; + public static final String QWER = "QWER"; - public ExampleCode(int value) { - this.value = value; - } + private String strField; + protected int aaa; + protected int abb; + public float foo; + private int value; - public String getX() { - String str = "Hello world!"; - String another = "one two three"; - if (another.equals(str)) { - System.out.println("Match found"); - } - return another; - } - - public void setValues(String val1, int val2) { - strField = val1; - aaa = abb = val2; - System.out.println(getX()); - System.out.println(foo()); // Ctrl+click shouldn't work for foo! - } - - /** Ctrl+click my stuff! */ - private class Inner1 { - - private int aaa; - private double d; - private String strField; - - public void doSomething(double newVal) { - d = newVal; - aaa = abb = clickable; - strField = getX(); // getX() clickable - } - - } - - /** Ctrl+click my stuff also! */ - private static class Inner2 { - - private double d; - private String strField; + public static ExampleCode getInstance() { + return new ExampleCode(0); + } + + public static HashMap getMap() { + + } + + public JTextField getTextField() { + return new JTextField("asdf"); + } + + public ExampleCode(int value) { + this.value = value; // both value variables are clickable, this.value goes to class field value, value goes to parameter value + } + + public void setSer(Serializable s) { - public void doSomething(double newVal) { - d = newVal; - aaa = abb = notClickable; - strField = getX(); // getX() not clickable + } + + public String getX() { + String str = "Hello world!"; + String another = "one two three"; + if (another.equals(str)) { + System.out.println("Match found"); + } + return another; + } + + public int getInt(int i, int j) { + return i + j; + } + + public void setValues(String val1, int val2) { + strField = val1; + aaa = abb = val2; + System.out.println(getX()); // ctrl-click should work, also completions for getX(). should pop up String methods/fields + System.out.println(foo()); // Ctrl+click shouldn't work for foo! + Inner1 in1 = new Inner1(); + Serializable k; + setSer(k); // check parameter completion, it should bring in1, k and val1 (since String implements Serializable!) and also brings some methods returning a valid Serializable class + // note that class Inner1 implements MySerializable interface which extends Serializable, the class hierarchy is followed correctly even for inner classes + } + + /** Ctrl+click my stuff! */ + private class Inner1 implements MySerializable { + + public Inner1() { + + } + + private int aaa; + private double d; + private String strField; + + public void doSomething(double newVal) { + d = newVal; + aaa = abb = clickable; + strField = getX(); // getX() clickable, refers to ExampleCode.this.getX() + System.out.println(ExampleCode.getMap().get("asdf").); // try completion here + System.out.println(ExampleCode.); // try completions here (this should be in the list, since Inner1 is not static) + System.out.println(ExampleCode.this.); // try completions here + + MyList x = new MyList<>(); + MyList buttons = new MyList<>(); + + for (JTextField textField : x) { + textField.getText(); // variable textField is recognized properly as JTextField type, completions come from proper class, try Ctrl-Click, should jump in for definition + } + + buttons.get(0).addActionListener(null); // Type is also properly recognized here, check the completions + x.get(aaa).getText(); // list x has JTextField type, so returned value by getter is a JTextField + x.add(0, getTextField()); // check parameter completion, it will offer getTextField as only option + x.add(1, new JTextField(ASDF)); // after new keyword, we get JTextField constructor completions + + JButton b; + + JTextField local = getTextField(); // check completion after the = sign, you will get getTextField as completion proposal + + buttons.add(getInt(aaa, abb), b); // check each method parameter position completion (Ctrl-Space before the parameter name to see the proposals) + + for (int i = 0;i < 100;i++) { + doWithInt(i); // check method parameter completion, it will offer i as local variable, Ctrl-Click will jump to i definition in the for statement + } + } + + public void doWithInt(int k) { + System.out.println("Current k: " + k); } - - } - -} \ No newline at end of file + } + + public static class MyList extends ArrayList { + public MyList() { + + } + } + + /** my serializable interface */ + private interface MySerializable extends Serializable { + } + + /** Ctrl+click my stuff also! */ + private static class Inner2 { + + private double d; + private String strField; + + public void doSomething(double newVal) { + d = newVal; // both d & newVal should be clickable + aaa = abb = notClickable; + strField = getX(); // getX() not clickable + ExampleCode.getInstance().setValues(strField, 0); // this should work (both completion & ctrl-click) + ExampleCode.this.getX(); // no completions should work for this, since it is in a static class, ctrl-click should also not working ExampleCode. should not pop up this in the list only static members + ExampleCode.getMap().put(strField, strField); // this should work (both completion & ctrl-click) + } + + } + +} \ No newline at end of file