/* * 03/21/2010 * * Copyright (C) 2010 Robert Futrell * robert_futrell at users.sourceforge.net * http://fifesoft.com/rsyntaxtextarea * * This library is distributed under a modified BSD license. See the included * RSTALanguageSupport.License.txt file for details. */ package org.fife.rsta.ac.java; import java.awt.Cursor; 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 javax.swing.text.BadLocationException; 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.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.rjc.lang.Type; import org.fife.rsta.ac.java.rjc.lang.TypeArgument; import org.fife.rsta.ac.java.rjc.lang.TypeParameter; 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.rsyntaxtextarea.Token; /** * Parses a Java AST for code completions. It currently scans the following: * * <ul> * <li>Import statements * <li>Method names * <li>Field names * </ul> * * Also, if the caret is inside a method, local variables up to the caret * position are also returned. * * @author Robert Futrell * @version 1.0 */ class SourceCompletionProvider extends DefaultCompletionProvider { /** * The parent completion provider. */ private JavaCompletionProvider javaProvider; /** * Used to get information about what classes match imports. */ private JarManager jarManager; private static final String JAVA_LANG_PACKAGE = "java.lang.*"; private static final String THIS = "this"; //Shorthand completions (templates and comments) private ShorthandCompletionCache shorthandCache; /** * Constructor. */ public SourceCompletionProvider() { this(null); } /** * Constructor. * * @param jarManager The jar manager for this provider. */ public SourceCompletionProvider(JarManager jarManager) { if (jarManager==null) { jarManager = new JarManager(); } this.jarManager = jarManager; setParameterizedCompletionParams('(', ", ", ')'); setAutoActivationRules(false, "."); // Default - only activate after '.' setParameterChoicesProvider(new SourceParamChoicesProvider()); } private void addCompletionsForStaticMembers(Set set, CompilationUnit cu, ClassFile cf, String pkg) { // Check us first, so if we override anything, we get the "newest" // version. int methodCount = cf.getMethodCount(); for (int i=0; i<methodCount; i++) { MethodInfo info = cf.getMethodInfo(i); if (isAccessible(info, pkg) && info.isStatic()) { MethodCompletion mc = new MethodCompletion(this, info); set.add(mc); } } int fieldCount = cf.getFieldCount(); for (int i=0; i<fieldCount; i++) { FieldInfo info = cf.getFieldInfo(i); if (isAccessible(info, pkg) && info.isStatic()) { FieldCompletion fc = new FieldCompletion(this, info); set.add(fc); } } ClassFile superClass = getClassFileFor(cu, cf.getSuperClassName(true)); if (superClass!=null) { addCompletionsForStaticMembers(set, cu, superClass, pkg); } } /** * Adds completions for accessible methods and fields of super classes. * This is only called when the caret is inside of a class. * TODO: Handle accessibility correctly! * * @param set * @param cu The compilation unit. * @param cf A class in the chain of classes that a type being parsed * inherits from. * @param pkg The package of the source being parsed. * @param typeParamMap A mapping of type parameters to type arguments * for the object whose fields/methods/etc. are currently being * code-completed. */ private void addCompletionsForExtendedClass(Set set, CompilationUnit cu, ClassFile cf, String pkg, Map typeParamMap) { // Reset this class's type-arguments-to-type-parameters map, so that // when methods and fields need to know type arguments, they can query // for them. cf.setTypeParamsToTypeArgs(typeParamMap); // Check us first, so if we override anything, we get the "newest" // version. int methodCount = cf.getMethodCount(); for (int i=0; i<methodCount; i++) { MethodInfo info = cf.getMethodInfo(i); // Don't show constructors if (isAccessible(info, pkg) && !info.isConstructor()) { MethodCompletion mc = new MethodCompletion(this, info); set.add(mc); } } int fieldCount = cf.getFieldCount(); for (int i=0; i<fieldCount; i++) { FieldInfo info = cf.getFieldInfo(i); if (isAccessible(info, pkg)) { FieldCompletion fc = new FieldCompletion(this, info); set.add(fc); } } // 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); } // 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<cf.getImplementedInterfaceCount(); i++) { String inter = cf.getImplementedInterfaceName(i, true); cf = getClassFileFor(cu, inter); addCompletionsForExtendedClass(set, cu, cf, pkg, typeParamMap); } } /** * Adds completions for all methods and public fields of a local variable. * This will add nothing if the local variable is a primitive type. * * @param cu The compilation unit being parsed. * @param var The local variable. * @param retVal The set to add completions to. */ private void addCompletionsForLocalVarsMethods(CompilationUnit cu, LocalVariable var, Set retVal) { Type type = var.getType(); String pkg = cu.getPackageName(); 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); if (cf!=null) { Map typeParamMap = createTypeParamMap(type, cf); addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); } } } /** * Adds simple shorthand completions relevant to Java. * * @param set The set to add to. */ private void addShorthandCompletions(Set set) { if(shorthandCache != null) { set.addAll(shorthandCache.getShorthandCompletions()); } } /** * Set template completion cache for source completion provider * @param templateCache */ public void setShorthandCache(ShorthandCompletionCache shorthandCache) { this.shorthandCache = shorthandCache; } /** * Gets the {@link ClassFile} for a class. * * @param cu The compilation unit being parsed. * @param className The name of the class (fully qualified or not). * @return The {@link ClassFile} for the class, or <code>null</code> if * <code>cf</code> represents <code>java.lang.Object</code> (or * if the super class could not be determined). */ private ClassFile getClassFileFor(CompilationUnit cu, String className) { //System.err.println(">>> Getting class file for: " + className); if (className==null) { return null; } ClassFile superClass = null; // Determine the fully qualified class to grab if (!Util.isFullyQualified(className)) { // Check in this source file's package first String pkg = cu.getPackageName(); if (pkg!=null) { String temp = pkg + "." + className; superClass = jarManager.getClassEntry(temp); } // Next, go through the imports (order is important) if (superClass==null) { for (Iterator i=cu.getImportIterator(); i.hasNext(); ) { ImportDeclaration id = (ImportDeclaration)i.next(); String imported = id.getName(); if (imported.endsWith(".*")) { String temp = imported.substring( 0, imported.length()-1) + className; superClass = jarManager.getClassEntry(temp); if (superClass!=null) { break; } } else if (imported.endsWith("." + className)) { superClass = jarManager.getClassEntry(imported); break; } } } // Finally, try java.lang if (superClass==null) { String temp = "java.lang." + className; superClass = jarManager.getClassEntry(temp); } } else { superClass = jarManager.getClassEntry(className); } return superClass; } /** * Adds completions for local variables in a method. * * @param set * @param method * @param offs The caret's offset into the source. This should be inside * of <code>method</code>. */ private void addLocalVarCompletions(Set set, Method method, int offs) { for (int i=0; i<method.getParameterCount(); i++) { FormalParameter param = method.getParameter(i); set.add(new LocalVariableCompletion(this, param)); } CodeBlock body = method.getBody(); if (body!=null) { addLocalVarCompletions(set, body, offs); } } /** * Adds completions for local variables in a code block inside of a method. * * @param set * @param block The code block. * @param offs The caret's offset into the source. This should be inside * of <code>block</code>. */ private void addLocalVarCompletions(Set set, CodeBlock block, int offs) { for (int i=0; i<block.getLocalVarCount(); i++) { LocalVariable var = block.getLocalVar(i); if (var.getNameEndOffset()<=offs) { set.add(new LocalVariableCompletion(this, var)); } else { // This and all following declared after offs break; } } for (int i=0; i<block.getChildBlockCount(); i++) { CodeBlock child = block.getChildBlock(i); if (child.containsOffset(offs)) { addLocalVarCompletions(set, child, offs); break; // All other blocks are past this one } // If we've reached a block that's past the offset we're // searching for... else if (child.getNameStartOffset()>offs) { break; } } } /** * Adds a jar to read from. * * @param info The jar to add. If this is <code>null</code>, then * the current JVM's main JRE jar (rt.jar, or classes.jar on OS X) * will be added. If this jar has already been added, adding it * again will do nothing (except possibly update its attached source * location). * @throws IOException If an IO error occurs. * @see #getJars() * @see #removeJar(File) */ public void addJar(LibraryInfo info) throws IOException { jarManager.addClassFileSource(info); } /** * Checks whether the user is typing a completion for a String member after * a String literal. * * @param comp The text component. * @param alreadyEntered The text already entered. * @param cu The compilation unit being parsed. * @param set The set to add possible completions to. * @return Whether the user is indeed typing a completion for a String * literal member. */ private boolean checkStringLiteralMember(JTextComponent comp, String alreadyEntered, CompilationUnit cu, Set set) { boolean stringLiteralMember = false; int offs = comp.getCaretPosition() - alreadyEntered.length() - 1; if (offs>1) { RSyntaxTextArea textArea = (RSyntaxTextArea)comp; RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); try { //System.out.println(doc.charAt(offs) + ", " + doc.charAt(offs+1)); if (doc.charAt(offs)=='"' && doc.charAt(offs+1)=='.') { int curLine = textArea.getLineOfOffset(offs); Token list = textArea.getTokenListForLine(curLine); Token prevToken = RSyntaxUtilities.getTokenAtOffset(list, offs); if (prevToken!=null && prevToken.getType()==Token.LITERAL_STRING_DOUBLE_QUOTE) { ClassFile cf = getClassFileFor(cu, "java.lang.String"); addCompletionsForExtendedClass(set, cu, cf, cu.getPackageName(), null); stringLiteralMember = true; } else { System.out.println(prevToken); } } } catch (BadLocationException ble) { // Never happens ble.printStackTrace(); } } return stringLiteralMember; } /** * Removes all jars from the "build path." * * @see #removeJar(File) * @see #addClassFileSource(JarInfo) * @see #getJars() */ public void clearJars() { jarManager.clearClassFileSources(); // The memory used by the completions can be quite large, so go ahead // and clear out the completions list so no-longer-needed ones are // eligible for GC. clear(); } /** * Creates and returns a mapping of type parameters to type arguments. * * @param type The type of a variable/field/etc. whose fields/methods/etc. * are being code completed, as declared in the source. This * includes type arguments. * @param cf The <code>ClassFile</code> representing the actual type of * the variable/field/etc. being code completed * @return A mapping of type parameter names to type arguments (both * Strings). */ private Map createTypeParamMap(Type type, ClassFile cf) { Map typeParamMap = null; List typeArgs = type.getTypeArguments(type.getIdentifierCount()-1); if (typeArgs!=null) { typeParamMap = new HashMap(); List paramTypes = cf.getParamTypes(); // Should be the same size! Otherwise, the source code has // too many/too few type arguments listed for this type. int min = Math.min(paramTypes==null ? 0 : paramTypes.size(), typeArgs.size()); for (int i=0; i<min; i++) { TypeArgument typeArg = (TypeArgument)typeArgs.get(i); typeParamMap.put(paramTypes.get(i), typeArg.toString()); } } return typeParamMap; } /** * {@inheritDoc} */ public List getCompletionsAt(JTextComponent tc, Point p) { getCompletionsImpl(tc); // Force loading of completions return super.getCompletionsAt(tc, p); } /** * {@inheritDoc} */ protected List getCompletionsImpl(JTextComponent comp) { comp.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { completions.clear(); CompilationUnit cu = javaProvider.getCompilationUnit(); if (cu==null) { return completions; // empty } Set set = new TreeSet(); // Cut down the list to just those matching what we've typed. // Note: getAlreadyEnteredText() never returns null String text = getAlreadyEnteredText(comp); // 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); } // 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); 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--; } } int end = Collections.binarySearch(completions, text+'{', comparator); end = -(end+1); return completions.subList(start, end); } finally { comp.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); } } /** * Returns the jars on the "build path." * * @return A list of {@link JarInfo}s. Modifying a <tt>JarInfo</tt> in * this list will have no effect on this completion provider; in * order to do that, you must re-add the jar via * {@link #addClassFileSource(JarInfo)}. If there are no jars on the * "build path," this will be an empty list. * @see #addClassFileSource(JarInfo) */ public List getJars() { return jarManager.getClassFileSources(); } public SourceLocation getSourceLocForClass(String className) { return jarManager.getSourceLocForClass(className); } /** * Returns whether a method defined by a super class is accessible to * this class. * * @param info Information about the member. * @param pkg The package of the source currently being parsed. * @return Whether or not the method is accessible. */ private boolean isAccessible(MemberInfo info, String pkg) { boolean accessible = false; int access = info.getAccessFlags(); if (org.fife.rsta.ac.java.classreader.Util.isPublic(access) || org.fife.rsta.ac.java.classreader.Util.isProtected(access)) { accessible = true; } else if (org.fife.rsta.ac.java.classreader.Util.isDefault(access)) { String pkg2 = info.getClassFile().getPackageName(); accessible = (pkg==null && pkg2==null) || (pkg!=null && pkg.equals(pkg2)); } return accessible; } /** * {@inheritDoc} */ protected boolean isValidChar(char ch) { return Character.isJavaIdentifierPart(ch) || ch=='.'; } /** * Loads completions based on the current caret location in the source. In * other words: * * <ul> * <li>If the caret is anywhere in a class, the names of all methods and * fields in the class are loaded. Methods and fields in super * classes are also loaded. TODO: Get super methods/fields added * correctly by access! * <li>If the caret is in a field, local variables currently accessible * are loaded. * </ul> * * @param cu * @param comp * @param alreadyEntered * @param retVal */ private void loadCompletionsForCaretPosition(CompilationUnit cu, JTextComponent comp, String alreadyEntered, Set retVal) { // Get completions for all fields and methods of all type declarations. //long startTime = System.currentTimeMillis(); int caret = comp.getCaretPosition(); //List temp = new ArrayList(); int start, end; int lastDot = alreadyEntered.lastIndexOf('.'); boolean qualified = lastDot>-1; String prefix = qualified ? alreadyEntered.substring(0, lastDot) : null; for (Iterator i=cu.getTypeDeclarationIterator(); i.hasNext(); ) { TypeDeclaration td = (TypeDeclaration)i.next(); start = td.getBodyStartOffset(); end = td.getBodyEndOffset(); if (caret>start && caret<=end) { loadCompletionsForCaretPosition(cu, comp, alreadyEntered, retVal, td, prefix, caret); } else if (caret<start) { break; // We've passed any type declarations we could be in } } //long time = System.currentTimeMillis() - startTime; //System.out.println("methods/fields/localvars loaded in: " + time); } /** * Loads completions based on the current caret location in the source. * This method is called when the caret is found to be in a specific type * declaration. This method checks if the caret is in a child type * declaration first, then adds completions for itself next. * * <ul> * <li>If the caret is anywhere in a class, the names of all methods and * fields in the class are loaded. Methods and fields in super * classes are also loaded. TODO: Get super methods/fields added * correctly by access! * <li>If the caret is in a field, local variables currently accessible * are loaded. * </ul> * * @param cu * @param comp * @param alreadyEntered * @param retVal */ private void loadCompletionsForCaretPosition(CompilationUnit cu, JTextComponent comp, String alreadyEntered, Set retVal, TypeDeclaration td, String prefix, int caret) { // 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<td.getChildTypeCount(); i++) { TypeDeclaration childType = td.getChildType(i); loadCompletionsForCaretPosition(cu, comp, alreadyEntered, retVal, childType, prefix, caret); } Method currentMethod = null; Map typeParamMap = new HashMap(); if (td instanceof NormalClassDeclaration) { NormalClassDeclaration ncd = (NormalClassDeclaration)td; List typeParams = ncd.getTypeParameters(); if (typeParams!=null) { for (int i=0; i<typeParams.size(); i++) { TypeParameter typeParam = (TypeParameter)typeParams.get(i); String typeVar = typeParam.getName(); // For non-qualified completions, use type var name. typeParamMap.put(typeVar, typeVar); } } } // 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(); for (Iterator j=td.getMemberIterator(); j.hasNext(); ) { Member m = (Member)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<method.getBodyEndOffset()) { currentMethod = method; // Don't add completions for local vars if there is // a prefix, even "this". if (prefix==null) { addLocalVarCompletions(retVal, method, caret); } } } else if (m instanceof Field) { if (prefix==null || THIS.equals(prefix)) { Field field = (Field)m; retVal.add(new FieldCompletion(this, field)); } } } // Completions for superclass methods. // TODO: Implement me better if (prefix==null || THIS.equals(prefix)) { if (td instanceof NormalClassDeclaration) { NormalClassDeclaration ncd = (NormalClassDeclaration)td; Type extended = ncd.getExtendedType(); if (extended!=null) { // e.g., not java.lang.Object String superClassName = extended.toString(); ClassFile cf = getClassFileFor(cu, superClassName); if (cf!=null) { addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); } else { System.out.println("[DEBUG]: Couldn't find ClassFile for: " + superClassName); } } } } // Completions for methods of fields, return values of methods, // static fields/methods, etc. if (prefix!=null && !THIS.equals(prefix)) { loadCompletionsForCaretPositionQualified(cu, alreadyEntered, retVal, td, currentMethod, prefix, caret); } } /** * Loads completions for the text at the current caret position, if there * is a "prefix" of chars and at least one '.' character in the text up to * the caret. This is currently very limited and needs to be improved. * * @param cu * @param alreadyEntered * @param retVal * @param td The type declaration the caret is in. * @param currentMethod The method the caret is in, or <code>null</code> if * none. * @param prefix The text up to the current caret position. This is * guaranteed to be non-<code>null</code> not equal to * "<tt>this</tt>". * @param offs The offset of the caret in the document. */ 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"); 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 = (Member)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<cf.getImplementedInterfaceCount(); i++) { String inter = cf.getImplementedInterfaceName(i, true); cf = getClassFileFor(cu, inter); System.out.println(cf); } } } matched = true; break; } } } // The prefix might be for a local variable in the current method. if (currentMethod!=null) { boolean found = false; // Check parameters to the current method 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)) { addCompletionsForLocalVarsMethods(cu, param, retVal); found = true; break; } } // If a formal param wasn't matched, check local variables. if (!found) { CodeBlock body = currentMethod.getBody(); if (body!=null) { loadCompletionsForCaretPositionQualifiedCodeBlock(cu, retVal, td, body, prefix, offs); } } matched |= found; } // Could be a class name, in which case we'll need to add completions // for static fields and methods. if (!matched) { List imports = cu.getImports(); List matches = jarManager.getClassesWithUnqualifiedName( prefix, imports); if (matches!=null) { for (int i=0; i<matches.size(); i++) { ClassFile cf = (ClassFile)matches.get(i); addCompletionsForStaticMembers(retVal, cu, cf, pkg); } } } } private void loadCompletionsForCaretPositionQualifiedCodeBlock( CompilationUnit cu, Set retVal, TypeDeclaration td, CodeBlock block, String prefix, int offs) { boolean found = false; for (int i=0; i<block.getLocalVarCount(); i++) { LocalVariable var = block.getLocalVar(i); if (var.getNameEndOffset()<=offs) { // TODO: This assumes prefix is "1 level deep" if (prefix.equals(var.getName())) { addCompletionsForLocalVarsMethods(cu, var, retVal); found = true; break; } } else { // This and all following declared after offs break; } } if (found) { return; } for (int i=0; i<block.getChildBlockCount(); i++) { CodeBlock child = block.getChildBlock(i); if (child.containsOffset(offs)) { loadCompletionsForCaretPositionQualifiedCodeBlock(cu, retVal, td, child, prefix, offs); break; // All other blocks are past this one } // If we've reached a block that's past the offset we're // searching for... else if (child.getNameStartOffset()>offs) { break; } } } /** * Loads completions for a single import statement. * * @param importStr The import statement. * @param pkgName The package of the source currently being parsed. */ private void loadCompletionsForImport(Set set, String importStr, String pkgName) { if (importStr.endsWith(".*")) { String pkg = importStr.substring(0, importStr.length()-2); boolean inPkg = pkg.equals(pkgName); List classes = jarManager.getClassesInPackage(pkg, inPkg); for (Iterator i=classes.iterator(); i.hasNext(); ) { ClassFile cf = (ClassFile)i.next(); set.add(new ClassCompletion(this, cf)); } } else { ClassFile cf = jarManager.getClassEntry(importStr); if (cf!=null) { set.add(new ClassCompletion(this, cf)); } } } /** * Loads completions for all import statements. * * @param cu The compilation unit being parsed. */ private void loadImportCompletions(Set set, String text, CompilationUnit cu) { // Fully-qualified completions are handled elsewhere, so no need to // duplicate the work here if (text.indexOf('.')>-1) { return; } //long startTime = System.currentTimeMillis(); String pkgName = cu.getPackageName(); loadCompletionsForImport(set, JAVA_LANG_PACKAGE, pkgName); for (Iterator i=cu.getImportIterator(); i.hasNext(); ) { ImportDeclaration id = (ImportDeclaration)i.next(); String name = id.getName(); if (!JAVA_LANG_PACKAGE.equals(name)) { loadCompletionsForImport(set, name, pkgName); } } // Collections.sort(completions); //long time = System.currentTimeMillis() - startTime; //System.out.println("imports loaded in: " + time); } /** * Removes a jar from the "build path." * * @param jar The jar to remove. * @return Whether the jar was removed. This will be <code>false</code> * if the jar was not on the build path. * @see #addClassFileSource(JarInfo) * @see #getJars() * @see #clearJars() */ public boolean removeJar(File jar) { boolean removed = jarManager.removeClassFileSource(jar); // The memory used by the completions can be quite large, so go ahead // and clear out the completions list so no-longer-needed ones are // eligible for GC. if (removed) { clear(); } return removed; } /** * Sets the parent Java provider. * * @param javaProvider The parent completion provider. */ void setJavaProvider(JavaCompletionProvider javaProvider) { this.javaProvider = javaProvider; } }