All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.rsta.ac.java.SourceCompletionProvider Maven / Gradle / Ivy

/*
 * 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.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.rsyntaxtextarea.Token;


/**
 * Parses a Java AST for code completions.  It currently scans the following:
 * 
 * 
    *
  • Import statements *
  • Method names *
  • Field names *
* * 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 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 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 shorthandCache The new cache. */ 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 null if * cf represents java.lang.Object (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) { Iterator i = cu.getImportIterator(); while (i.hasNext()) { ImportDeclaration id = 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 method. */ private void addLocalVarCompletions(Set set, Method method, int offs) { for (int i=0; iblock
. */ private void addLocalVarCompletions(Set set, CodeBlock block, int offs) { for (int i=0; ioffs) { break; } } } /** * Adds a jar to read from. * * @param info The jar to add. If this is null, 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 #addJar(LibraryInfo) * @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 ClassFile 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 getCompletionsAt(JTextComponent tc, Point p) { getCompletionsImpl(tc); // Force loading of completions return super.getCompletionsAt(tc, p); } /** * {@inheritDoc} */ @Override protected List getCompletionsImpl(JTextComponent comp) { comp.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { completions = new ArrayList();//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); @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)); } } /** * Returns the jars on the "build path." * * @return A list of {@link LibraryInfo}s. Modifying a * LibraryInfo in this list will have no effect on * this completion provider; in order to do that, you must re-add * the jar via {@link #addJar(LibraryInfo)}. If there are * no jars on the "build path," this will be an empty list. * @see #addJar(LibraryInfo) */ 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} */ @Override protected boolean isValidChar(char ch) { return Character.isJavaIdentifierPart(ch) || ch=='.'; } /** * Loads completions based on the current caret location in the source. In * other words: * *
    *
  • 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! *
  • If the caret is in a field, local variables currently accessible * are loaded. *
* * @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; 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 *
  • 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! *
  • If the caret is in a field, local variables currently accessible * are loaded. * * * @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 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); } } } // 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() && caretnull if * none. * @param prefix The text up to the current caret position. This is * guaranteed to be non-null not equal to * "this". * @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 = 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 imports = cu.getImports(); List matches = jarManager.getClassesWithUnqualifiedName( prefix, imports); if (matches!=null) { for (int i=0; i retVal, TypeDeclaration td, CodeBlock block, String prefix, int offs) { boolean found = false; for (int i=0; ioffs) { 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 (ClassFile cf : classes) { 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 = 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 false * if the jar was not on the build path. * @see #addJar(LibraryInfo) * @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; } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy