blob: 0293ee40e85b13bf52d792338ea016bba6e70d84 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.navigation;
import com.sun.source.tree.Tree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.swing.SwingUtilities;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CancellableTask;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.ui.ElementJavadoc;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.java.navigation.base.Utils;
import org.openide.filesystems.FileObject;
import org.openide.util.Pair;
/**
* This task is called every time the caret position changes in a Java editor.
* <p>
* The task finds the TreePath of the Tree under the caret, converts it to
* an Element and then shows the javadoc in the Javadoc window.
*
* @author Sandip V. Chitale (Sandip.Chitale@Sun.Com)
*/
public class CaretListeningTask implements CancellableTask<CompilationInfo> {
private FileObject fileObject;
private final AtomicBoolean canceled;
private static ElementHandle<Element> lastEh;
private static ElementHandle<Element> lastEhForNavigator;
private static final Set<JavaTokenId> TOKENS_TO_SKIP = EnumSet.of(JavaTokenId.WHITESPACE,
JavaTokenId.BLOCK_COMMENT,
JavaTokenId.LINE_COMMENT,
JavaTokenId.JAVADOC_COMMENT);
CaretListeningTask(FileObject fileObject) {
this.fileObject = fileObject;
this.canceled = new AtomicBoolean();
}
static void resetLastEH() {
lastEh = null;
}
public void run(CompilationInfo compilationInfo) throws Exception {
// System.out.println("running " + fileObject);
resume();
boolean navigatorShouldUpdate = ClassMemberPanel.getInstance() != null; // XXX set by navigator visible
boolean javadocShouldUpdate = JavadocTopComponent.shouldUpdate();
if ( isCancelled() || ( !navigatorShouldUpdate && !javadocShouldUpdate ) ) {
return;
}
int lastPosition = CaretListeningFactory.getLastPosition(fileObject);
TokenHierarchy tokens = compilationInfo.getTokenHierarchy();
TokenSequence ts = tokens.tokenSequence();
boolean inJavadoc = false;
int offset = ts.move(lastPosition);
if (ts.moveNext() && ts.token() != null ) {
Token token = ts.token();
TokenId tid = token.id();
if ( tid == JavaTokenId.JAVADOC_COMMENT ) {
inJavadoc = true;
}
if ( tid == JavaTokenId.WHITESPACE && shouldGoBack(token.text().toString(), offset < 0 ? 0 : offset ) ) {
if ( ts.movePrevious() ) {
token = ts.token();
tid = token.id();
}
}
if ( TOKENS_TO_SKIP.contains(tid) ) {
skipTokens(ts, TOKENS_TO_SKIP);
}
lastPosition = ts.offset();
}
if (ts.token() != null && (ts.token().length() > 1 || ts.token().id() == JavaTokenId.AT)) {
// it is magic for TreeUtilities.pathFor to proper tree
++lastPosition;
}
// Find the TreePath for the caret position
TreePath tp =
compilationInfo.getTreeUtilities().pathFor(lastPosition);
Tree.Kind k = tp.getLeaf().getKind();
if (TreeUtilities.CLASS_TREE_KINDS.contains(k) && ts.token() != null && ts.token().length() == 1) {
// in case of single-char token, try next position, since e.g. method or a field
// can have single-char type declaration.
TreePath tp2 = compilationInfo.getTreeUtilities().pathFor(lastPosition + 1);
SourcePositions sp = compilationInfo.getTrees().getSourcePositions();
long e1 = sp.getEndPosition(compilationInfo.getCompilationUnit(), tp2.getLeaf());
long e2 = sp.getEndPosition(compilationInfo.getCompilationUnit(), tp.getLeaf());
// the "inner" member does not extend beyond the class
if (e2 != -1 && e1 != -1 && e1 <= e2) {
TreePath p = tp2;
while (p != null && p.getLeaf() != tp.getLeaf() && p.getParentPath() != null) {
if (p.getParentPath().getLeaf() == tp.getLeaf()) {
// class member found in between position+1 and position. Use the class member
tp = tp2;
break;
}
p = p.getParentPath();
}
}
}
// if cancelled, return
if (isCancelled()) {
return;
}
// Update the navigator
if ( navigatorShouldUpdate ) {
updateNavigatorSelection(compilationInfo, tp);
}
// Get Element
Element element = compilationInfo.getTrees().getElement(tp);
// if cancelled or no element, return
if (isCancelled() ) {
return;
}
if ( element == null || inJavadoc ) {
final Pair<Element,TreePath> p = outerElement(compilationInfo, tp);
element = p != null ? p.first() : null;
}
// if is canceled or no element
if (isCancelled() || element == null) {
return;
}
// Don't update when element is the same
if (Utils.signatureEquals(lastEh, element) && !inJavadoc) {
// System.out.println(" stoped because os same eh");
return;
} else {
switch (element.getKind()) {
case PACKAGE:
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
case METHOD:
case CONSTRUCTOR:
case INSTANCE_INIT:
case STATIC_INIT:
case FIELD:
case ENUM_CONSTANT:
case MODULE:
lastEh = Utils.createElementHandle(element);
// Different element clear data
setJavadoc(null, null); // NOI18N
break;
case TYPE_PARAMETER:
case PARAMETER:
element = element.getEnclosingElement(); // Take the enclosing method
if (element != null && element.asType() != null) {
lastEh = Utils.createElementHandle(element);
} else {
lastEh = null;
}
setJavadoc(null, null); // NOI18N
break;
case LOCAL_VARIABLE:
lastEh = null; // ElementHandle not supported
setJavadoc(null, null); // NOI18N
return;
default:
// clear
setJavadoc(null, null); // NOI18N
return;
}
}
// Compute and set javadoc
if ( javadocShouldUpdate ) {
// System.out.println("Updating JD");
computeAndSetJavadoc(compilationInfo, element);
}
if ( isCancelled() ) {
return;
}
}
private void setJavadoc(final FileObject owner, final ElementJavadoc javadoc) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JavadocTopComponent javadocTopComponent = JavadocTopComponent.findInstance();
if (javadocTopComponent != null && javadocTopComponent.isOpened()) {
javadocTopComponent.setJavadoc(owner, javadoc);
}
}
});
}
/**
* After this method is called the task if running should exit the run
* method immediately.
*/
@Override
public final void cancel() {
canceled.set(true);
}
protected final boolean isCancelled() {
return canceled.get();
}
protected final void resume() {
canceled.set(false);
}
private void computeAndSetJavadoc(CompilationInfo compilationInfo, Element element) {
if (isCancelled()) {
return;
}
setJavadoc(compilationInfo.getFileObject(), ElementJavadoc.create(
compilationInfo,
element,
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return isCancelled();
}
}));
}
private void updateNavigatorSelection(CompilationInfo ci, TreePath tp) throws Exception {
final ClassMemberPanel cmp = ClassMemberPanel.getInstance();
if (cmp == null) {
return;
}
final ClassMemberPanelUI cmpUi = cmp.getClassMemberPanelUI();
if (!cmpUi.isAutomaticRefresh()) {
cmpUi.getTask().runImpl(ci, false);
lastEhForNavigator = null;
}
// Try to find the declaration we are in
final Pair<Element,TreePath> p = outerElement(ci, tp);
if (p != null) {
final Element e = p.first();
Runnable action = null;
if (e == null) {
//Directive
lastEhForNavigator = null;
action = () -> {
cmp.selectTreePath(TreePathHandle.create(p.second(), ci));
};
} else if (e.getKind() != ElementKind.OTHER) {
final ElementHandle<Element> eh = ElementHandle.create(e);
if (lastEhForNavigator != null && eh.signatureEquals(lastEhForNavigator)) {
return;
}
lastEhForNavigator = eh;
action = () -> {
cmp.selectElement(eh);
};
}
if (action != null) {
SwingUtilities.invokeLater(action);
}
}
}
private static Pair<Element,TreePath> outerElement( CompilationInfo ci, TreePath tp ) {
Pair<Element,TreePath> res = null;
while (tp != null) {
switch( tp.getLeaf().getKind()) {
case METHOD:
case ANNOTATION_TYPE:
case CLASS:
case ENUM:
case INTERFACE:
case COMPILATION_UNIT:
case MODULE:
{
final Element e = ci.getTrees().getElement(tp);
if (e != null) {
res = Pair.of(e,tp);
}
break;
}
case REQUIRES:
case EXPORTS:
case OPENS:
case PROVIDES:
case USES:
{
res = Pair.of(null, tp);
break;
}
case VARIABLE:
{
final Element e = ci.getTrees().getElement(tp);
if (e != null && e.getKind().isField()) {
res = Pair.of(e,tp);
}
break;
}
}
if ( res != null ) {
break;
}
tp = tp.getParentPath();
}
return res;
}
private void skipTokens( TokenSequence ts, Set<JavaTokenId> typesToSkip ) {
while(ts.moveNext()) {
if ( !typesToSkip.contains(ts.token().id()) ) {
return;
}
}
return;
}
private boolean shouldGoBack( String s, int offset ) {
int nlBefore = 0;
int nlAfter = 0;
for( int i = 0; i < s.length(); i++ ) {
if ( s.charAt(i) == '\n' ) { // NOI18N
if ( i < offset ) {
nlBefore ++;
}
else {
nlAfter++;
}
if ( nlAfter > nlBefore ) {
return true;
}
}
}
if ( nlBefore < nlAfter ) {
return false;
}
return offset < (s.length() - offset);
}
}