Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.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.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:
*
*
*
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 '.'
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; inull 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) {
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);
}
/**
* 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.type==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(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();
}
/**
* 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; i0 &&
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 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, "java.lang.Object");
addCompletionsForExtendedClass(retVal, cu, cf, pkg, null);
FieldCompletion fc = FieldCompletion.
createLengthCompletion(this, type.toString());
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; 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;
}
}