Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,6 @@ public void run() {
if (done || resultChanged) {
lastSize = newSize[0];
lastProvCount = newProvCount;
model.remove(toRemove);
model.add(toAdd);
attrCopier.checkWrongCase(toRemove, toAdd);
if ( isCanceled ) {
LOG.log(
Expand All @@ -405,6 +403,17 @@ public void run() {
Level.FINE,
"Worker for text {0} finished after {1} ms.", //NOI18N
new Object[]{text, System.currentTimeMillis() - createTime});
/* NB: model.remove and model.add must happen inside
the invokeLater runnable, guarded by !isCanceled,
so that
(a) the remove and add are a single EDT turn (no
intervening repaint/keystroke between them),
and
(b) a Worker that was cancelled after
getSymbolNames cannot mutate the live model
one final time. The cancel flag is set from
the EDT in setListModel, so checking it on
the EDT here is race-free. */
SwingUtilities.invokeLater(() -> {
if (done) {
final Pair<String, String> nameAndScope = Utils.splitNameAndScope(text);
Expand All @@ -418,6 +427,8 @@ public void run() {
nameAndScope.second());
}
if (!isCanceled) {
model.remove(toRemove);
model.add(toAdd);
enableOK(panel.setModel(model, done));
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,59 +19,37 @@

package org.netbeans.modules.java.source.ui;

import com.sun.tools.javac.api.ClientCodeWrapper;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.jvm.ClassReader;
import com.sun.tools.javac.model.JavacElements;

import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.swing.Icon;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.queries.SourceLevelQuery;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.ui.ElementOpen;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.modules.java.source.ElementHandleAccessor;
import org.netbeans.modules.java.source.ElementUtils;
import org.netbeans.modules.java.source.indexing.JavaIndex;
import org.netbeans.modules.java.source.parsing.CachingArchiveProvider;
import org.netbeans.modules.java.source.parsing.CachingFileManager;
import org.netbeans.modules.java.source.parsing.JavacParser;
import org.netbeans.modules.java.source.usages.ClassIndexImpl;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.jumpto.support.AsyncDescriptor;
import org.netbeans.spi.jumpto.support.DescriptorChangeEvent;
import org.netbeans.spi.jumpto.support.DescriptorChangeListener;
import org.netbeans.spi.jumpto.symbol.SymbolDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.util.BaseUtilities;
import org.openide.util.Exceptions;
import org.openide.util.Pair;
import org.openide.util.Parameters;
Expand All @@ -85,11 +63,6 @@ final class AsyncJavaSymbolDescriptor extends JavaSymbolDescriptorBase implement

private static final RequestProcessor WORKER = new RequestProcessor(AsyncJavaSymbolDescriptor.class);
private static final String INIT = "<init>"; //NOI18N
private static final Logger LOG = Logger.getLogger(AsyncJavaSymbolDescriptor.class.getName());
private static volatile boolean pkgROELogged = false;
private static volatile boolean clzROELogged = false;

private static Reference<JavacTaskImpl> javacRef;

private final String ident;
private final boolean caseSensitive;
Expand Down Expand Up @@ -131,6 +104,19 @@ public String getSimpleName() {
@Override
public void open() {
final Collection<? extends SymbolDescriptor> symbols = resolve();
/* resolve() falls back to Collections.singleton(this) if it could
not enrich the descriptor (e.g. javac threw CompletionFailure
for some transient reason). Detect that case explicitly to
avoid recursing into our own open(); fall back to opening the
type by its ElementHandle via ElementOpen, which goes through
ClassIndex/SourceUtils. */
if (symbols.size() == 1 && symbols.iterator().next() == this) {
final FileObject file = getFileObject();
if (file != null) {
ElementOpen.open(ClasspathInfo.create(file), getOwner());
}
return;
}
if (!symbols.isEmpty()) {
symbols.iterator().next().open();
}
Expand Down Expand Up @@ -197,26 +183,40 @@ private void fireDescriptorChange(Collection<? extends SymbolDescriptor> replace

@NonNull
private Collection<? extends SymbolDescriptor> resolve() {
final List<SymbolDescriptor> symbols = new ArrayList<>();
try {
final List<SymbolDescriptor> symbols = new ArrayList<>();
final JavacTaskImpl jt = getJavac(getRoot());
// final JavaFileManager fm = jt.getContext().get(JavaFileManager.class);
// final ClassReader cr = ClassReader.instance(jt.getContext());
// final Names names = Names.instance(jt.getContext());
// final String binName = ElementHandleAccessor.getInstance().getJVMSignature(getOwner())[0];
// final Name jcName = names.fromString(binName);
// final Symbol.ClassSymbol te = cr.enterClass((com.sun.tools.javac.util.Name) jcName);
// te.owner.completer = null;
// te.classfile = fm.getJavaFileForInput(
// StandardLocation.CLASS_PATH,
// FileObjects.convertFolder2Package(binName),
// JavaFileObject.Kind.CLASS);
final Symtab syms = Symtab.instance(jt.getContext());
final Set<?> pkgs = new HashSet<>(getPackages(syms).keySet());
final Set<?> clzs = new HashSet<>(getClasses(syms).keySet());
jt.getElements().getTypeElement("java.lang.Object"); // Ensure proper javac initialization
final TypeElement te = ElementUtils.getTypeElementByBinaryName(jt,
ElementHandleAccessor.getInstance().getJVMSignature(getOwner())[0]);
final String binName = ElementHandleAccessor.getInstance().getJVMSignature(getOwner())[0];
/* Build the JavacTask via JavacParser.createJavacTask
(which pre-registers NetBeans' javac context enhancers --
most importantly NBClassReader, plus NBAttr, NBEnter,
NBMemberEnter, NBResolve, NBClassFinder, NBJavaCompiler,
NBClassWriter, etc.) instead of calling
JavacTool.create().getTask(...) directly. NetBeans .sig
files (the per-root cached form of indexed Java classes)
are written by NBClassWriter and use a relaxed/extended
class-file format that only NBClassReader knows how to
read; stock javac's ClassReader rejects them with
BadClassFile, which propagates as CompletionFailure.
ElementUtils.getTypeElementByBinaryName silently catches
the CompletionFailure and returns null, and resolve()
therefore returned an empty collection for every Java
type whose .sig had any NB-specific encoding -- the
actual cause of the long-standing Go to Symbol
disappearance bug. */
final ClasspathInfo cpInfo = ClasspathInfo.create(getRoot());
final JavacTaskImpl jt = JavacParser.createJavacTask(
cpInfo,
null,
null,
null,
null,
null,
null,
null,
Collections.<JavaFileObject>emptyList());
//Force JTImpl.prepareCompiler to get JTImpl into Context
jt.enter();
final TypeElement te = ElementUtils.getTypeElementByBinaryName(jt, binName);
if (te != null) {
if (ident.equals(getSimpleName(te, null, caseSensitive))) {
final String simpleName = te.getSimpleName().toString();
Expand Down Expand Up @@ -252,69 +252,31 @@ private Collection<? extends SymbolDescriptor> resolve() {
}
}
}
getClasses(syms).keySet().retainAll(clzs);
getPackages(syms).keySet().retainAll(pkgs);
return symbols;
} catch (IOException e) {
} catch (RuntimeException e) {
/* Swallow so that unexpected javac failures (e.g.
CompletionFailure, which is a RuntimeException) fall through
to the "no enriched symbols" path below rather than
propagating into the WORKER thread and leaving the descriptor
in a half-initialized state. Previously this was a catch for
IOException, thrown by the old hand-rolled JavaFileManager
wiring that has now been deleted. */
Exceptions.printStackTrace(e);
return Collections.<SymbolDescriptor>emptyList();
}
}

@NonNull
private Map<?,?> getPackages(final Symtab cr) {
Map<?,?> res = Collections.emptyMap();
try {
final Field fld = ClassReader.class.getDeclaredField("packages"); //NOI18N
fld.setAccessible(true);
final Map<?,?> pkgs = (Map<?,?>) fld.get(cr);
if (pkgs != null) {
res = pkgs;
}
} catch (ReflectiveOperationException e) {
if (!pkgROELogged) {
LOG.warning(e.getMessage());
pkgROELogged = true;
}
/* The async path is meant to *enrich* a descriptor that the
(Lucene-backed) JavaSymbolProvider already produced -- not to
delete it. If javac couldn't load the TypeElement (te==null),
no enclosed element matched ident, or the lookup threw, the
entry is still a valid match from the index and must remain
in the list. Returning an empty collection here would cause
Models.MutableListModelImpl#descriptorChanged to remove the
source from the live model, producing the long-standing
"search results appear briefly then disappear" Go to Symbol
symptom. Fall back to keeping the AsyncJavaSymbolDescriptor
itself. */
if (symbols.isEmpty()) {
return Collections.<SymbolDescriptor>singleton(this);
}
return res;
}

@NonNull
private Map<?,?> getClasses(final Symtab cr) {
Map<?,?> res = Collections.emptyMap();
try {
final Field fld = ClassReader.class.getDeclaredField("classes"); //NOI18N
fld.setAccessible(true);
Map<?,?> clzs = (Map<?,?>) fld.get(cr);
if (clzs != null) {
res = clzs;
}
} catch (ReflectiveOperationException e) {
if (!clzROELogged) {
LOG.warning(e.getMessage());
clzROELogged = true;
}
}
return res;
}

private static JavacTaskImpl getJavac(FileObject root) throws IOException {
JavacTaskImpl javac;
Reference<JavacTaskImpl> ref = javacRef;
if (ref == null || (javac = ref.get()) == null) {
String sourceLevel = SourceLevelQuery.getSourceLevel(root);
javac = (JavacTaskImpl)JavacTool.create().getTask(null,
new RootChange(root),
new Listener(),
sourceLevel != null ? Arrays.asList("-source", sourceLevel) : Collections.<String>emptySet(), //NOI18N
Collections.<String>emptySet(),
Collections.<JavaFileObject>emptySet());
javacRef = new WeakReference<>(javac);
}
final JavaFileManager fm = javac.getContext().get(JavaFileManager.class);
((RootChange)fm).setRoot(root);
return javac;
return symbols;
}

@NonNull
Expand All @@ -332,104 +294,4 @@ private static String getSimpleName (
return result;
}

@ClientCodeWrapper.Trusted
private static final class RootChange implements JavaFileManager {

private FileObject currentRoot;
private JavaFileManager delegate;

RootChange(@NonNull final FileObject root) throws IOException {
setRoot(root);
}

void setRoot(@NonNull final FileObject root) throws IOException {
if (root != currentRoot) {
final File classes = JavaIndex.getClassFolder(root.toURL());
final CachingFileManager fm = new CachingFileManager(
CachingArchiveProvider.getDefault(),
ClassPathSupport.createClassPath(BaseUtilities.toURI(classes).toURL()),
null,
false,
true);
this.delegate = fm;
this.currentRoot = root;
}
}

@Override
public ClassLoader getClassLoader(Location location) {
return delegate.getClassLoader(location);
}

@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
return delegate.list(location, packageName, kinds, recurse);
}

@Override
public String inferBinaryName(Location location, JavaFileObject file) {
return delegate.inferBinaryName(location, file);
}

@Override
public boolean isSameFile(javax.tools.FileObject a, javax.tools.FileObject b) {
return delegate.isSameFile(a, b);
}

@Override
public boolean handleOption(String current, Iterator<String> remaining) {
return delegate.handleOption(current, remaining);
}

@Override
public boolean hasLocation(Location location) {
return location == StandardLocation.CLASS_PATH || location == StandardLocation.PLATFORM_CLASS_PATH;
}

@Override
public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
return delegate.getJavaFileForInput(location, className, kind);
}

@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, javax.tools.FileObject sibling) throws IOException {
return delegate.getJavaFileForOutput(location, className, kind, sibling);
}

@Override
public javax.tools.FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
return delegate.getFileForInput(location, packageName, relativeName);
}

@Override
public javax.tools.FileObject getFileForOutput(Location location, String packageName, String relativeName, javax.tools.FileObject sibling) throws IOException {
return delegate.getFileForOutput(location, packageName, relativeName, sibling);
}

@Override
public void flush() throws IOException {
delegate.flush();
}

@Override
public void close() throws IOException {
delegate.close();
}

@Override
public int isSupportedOption(String option) {
return delegate.isSupportedOption(option);
}

@Override
public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
return Collections.emptyList();
}
}

private static final class Listener implements DiagnosticListener<JavaFileObject> {
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
}
}
}
Loading
Loading