* Copyright (C) 2013 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.util.Collections;
import java.util.Iterator;
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.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;
* Checks for hyperlink-able tokens under the mouse position when Ctrl is
* pressed (Cmd on OS X). Currently this class only checks for accessible
* members in the current file only (e.g. no members in super classes, no other
* classes on the classpath, etc.). So naturally, there is a lot of room for
* improvement. IDE-style applications, for example, would want to check
* for members in super-classes, and open their source on click events.
// TODO: Anonymous inner classes probably aren't handled well.
class JavaLinkGenerator implements LinkGenerator {
private JavaLanguageSupport jls;
JavaLinkGenerator(JavaLanguageSupport jls) {
* Checks if the token at the specified offset is possibly a "click-able"
* @param textArea The text area.
* @param offs The offset, presumably at the mouse position.
* @return A result object.
private IsLinkableCheckResult checkForLinkableToken(
RSyntaxTextArea textArea, int offs) {
IsLinkableCheckResult result = null;
int line = textArea.getLineOfOffset(offs);
Token first = textArea.getTokenListForLine(line);
for (Token t=first; t!=null && t.isPaintable(); t=t.getNextToken()) {
if (t.containsPosition(offs)) {
// RSTA's tokens are pooled and re-used, so we must
// defensively make a copy of the one we want to keep!
Token token = new TokenImpl(t);
boolean isMethod = false;
prev = RSyntaxUtilities.getPreviousImportantToken(textArea, line-1);
if (prev!=null && prev.isSingleChar('.')) {
// Not a field or method defined in this class.
Token next = RSyntaxUtilities.getNextImportantToken(
t.getNextToken(), textArea, line);
if (next!=null && next.isSingleChar(Token.SEPARATOR, '(')) {
result = new IsLinkableCheckResult(token, isMethod);
else if (!t.isCommentOrWhitespace()) {
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea,
IsLinkableCheckResult result = checkForLinkableToken(textArea, offs);
JavaParser parser = jls.getParser(textArea);
CompilationUnit cu = parser.getCompilationUnit();
boolean method = result.method;
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) {
for (Iterator i=td.getMemberIterator(); i.hasNext(); ) {
Method m = null; // Nasty! Clean this code up
Member member = (Member)i.next();
// Check if a method or static block contains offs
if (member instanceof Method) {
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
String varName = t.getLexeme();
// Local variables first, in reverse order
List locals = block.getLocalVarsBefore(offs);
Collections.reverse(locals);
for (Iterator j=locals.iterator(); j.hasNext(); ) {
LocalVariable local = (LocalVariable)j.next();
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<m.getParameterCount(); j++) {
FormalParameter p = m.getParameter(j);
if (varName.equals(p.getName())) {
start = p.getNameStartOffset();
end = p.getNameEndOffset();
break; // No other code block will contain offs
// If no local var match, check fields or methods.
String varName = t.getLexeme();
Iterator i = method ? td.getMethodIterator() :
Member member = (Member)i.next();
if (((!deepestContainingMemberStatic && !staticFieldsOnly) || member.isStatic()) &&
varName.equals(member.getName())) {
start = member.getNameStartOffset();
end = member.getNameEndOffset();
// If still no match found, check parent type
staticFieldsOnly |= td.isStatic();
//td = td.isStatic() ? null : td.getParentType();
// Don't check for local vars in parent type methods.
return new SelectRegionLinkGeneratorResult(textArea, t.getOffset(),
* The result of checking whether a region of code under the mouse is
* <em>possibly</em> link-able.
private static class IsLinkableCheckResult {
* The token under the mouse position.
* Whether the token is a method invocation (as opposed to a local
private IsLinkableCheckResult(Token token, boolean method) {