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 code is licensed under the LGPL.  See the "license.txt" file included
 * with this project.
 */
package org.fife.rsta.ac.java;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.text.JTextComponent;

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.ui.autocomplete.DefaultCompletionProvider;


/**
 * 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"; /** * 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 '.' } 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; inull
if * cf represents java.lang.Object (or * if the super class could not be determined). */ private ClassFile getClassFileFor(CompilationUnit cu, String pkg, 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 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 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(JarInfo info) throws IOException { jarManager.addJar(info); } /** * Removes all jars from the "build path." * * @see #removeJar(File) * @see #addJar(JarInfo) * @see #getJars() */ public void clearJars() { jarManager.clearJars(); // 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(); } /** * {@inheritDoc} */ protected List getCompletionsImpl(JTextComponent comp) { comp.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.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. String text = getAlreadyEnteredText(comp); // 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 is 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(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.TEXT_CURSOR)); } } /** * Returns the jars on the "build path." * * @return A list of {@link JarInfo}s. Modifying a JarInfo 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(JarInfo)}. If there are no jars on the * "build path," this will be an empty list. * @see #addJar(JarInfo) */ public List getJars() { return jarManager.getJars(); } public File 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: * *
    *
  • 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; 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 *
  • 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=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 = (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, pkg, "java.lang.Object"); addCompletionsForExtendedClass(retVal, cu, cf, pkg); FieldCompletion fc = FieldCompletion. createLengthCompletion(this, type.toString()); retVal.add(fc); } else if (!type.isBasicType()) { String typeStr = type.toString(); ClassFile cf = getClassFileFor(cu, pkg, typeStr); if (cf!=null) { addCompletionsForExtendedClass(retVal, cu, cf, pkg); } } 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; 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 (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 false * if the jar was not on the build path. * @see #addJar(JarInfo) * @see #getJars() * @see #clearJars() */ public boolean removeJar(File jar) { boolean removed = jarManager.removeJar(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