SourceCodeAnalysisImpl.java revision 4088:33c818a75ec9
1/*
2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.jshell;
27
28import jdk.jshell.SourceCodeAnalysis.Completeness;
29import com.sun.source.tree.AssignmentTree;
30import com.sun.source.tree.ClassTree;
31import com.sun.source.tree.CompilationUnitTree;
32import com.sun.source.tree.ErroneousTree;
33import com.sun.source.tree.ExpressionTree;
34import com.sun.source.tree.IdentifierTree;
35import com.sun.source.tree.ImportTree;
36import com.sun.source.tree.MemberSelectTree;
37import com.sun.source.tree.MethodInvocationTree;
38import com.sun.source.tree.MethodTree;
39import com.sun.source.tree.NewClassTree;
40import com.sun.source.tree.Scope;
41import com.sun.source.tree.Tree;
42import com.sun.source.tree.Tree.Kind;
43import com.sun.source.tree.TypeParameterTree;
44import com.sun.source.tree.VariableTree;
45import com.sun.source.util.SourcePositions;
46import com.sun.source.util.TreePath;
47import com.sun.source.util.TreePathScanner;
48import com.sun.tools.javac.api.JavacScope;
49import com.sun.tools.javac.code.Flags;
50import com.sun.tools.javac.code.Symbol.CompletionFailure;
51import com.sun.tools.javac.code.Symbol.VarSymbol;
52import com.sun.tools.javac.code.Symtab;
53import com.sun.tools.javac.code.Type;
54import com.sun.tools.javac.code.Type.ClassType;
55import jdk.internal.shellsupport.doc.JavadocHelper;
56import com.sun.tools.javac.util.Name;
57import com.sun.tools.javac.util.Names;
58import com.sun.tools.javac.util.Pair;
59import jdk.jshell.CompletenessAnalyzer.CaInfo;
60import jdk.jshell.TaskFactory.AnalyzeTask;
61import jdk.jshell.TaskFactory.ParseTask;
62
63import java.util.ArrayList;
64import java.util.Collections;
65import java.util.Iterator;
66import java.util.List;
67import java.util.Objects;
68import java.util.function.Predicate;
69
70import javax.lang.model.element.Element;
71import javax.lang.model.element.ElementKind;
72import javax.lang.model.element.Modifier;
73import javax.lang.model.element.TypeElement;
74import javax.lang.model.type.DeclaredType;
75import javax.lang.model.type.TypeMirror;
76
77import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA;
78
79import java.io.IOException;
80import java.net.URI;
81import java.nio.file.DirectoryStream;
82import java.nio.file.FileSystem;
83import java.nio.file.FileSystems;
84import java.nio.file.FileVisitResult;
85import java.nio.file.FileVisitor;
86import java.nio.file.Files;
87import java.nio.file.Path;
88import java.nio.file.Paths;
89import java.nio.file.attribute.BasicFileAttributes;
90import java.util.Arrays;
91import java.util.Collection;
92import java.util.Comparator;
93import java.util.EnumSet;
94import java.util.HashMap;
95import java.util.HashSet;
96import java.util.LinkedHashSet;
97import java.util.Map;
98import java.util.NoSuchElementException;
99import java.util.Set;
100import java.util.concurrent.ExecutorService;
101import java.util.concurrent.Executors;
102import java.util.function.Function;
103import java.util.regex.Matcher;
104import java.util.regex.Pattern;
105import java.util.stream.Collectors;
106
107import static java.util.stream.Collectors.collectingAndThen;
108import static java.util.stream.Collectors.toCollection;
109import static java.util.stream.Collectors.toList;
110import static java.util.stream.Collectors.toSet;
111
112import java.util.stream.Stream;
113import java.util.stream.StreamSupport;
114
115import javax.lang.model.SourceVersion;
116
117import javax.lang.model.element.ExecutableElement;
118import javax.lang.model.element.PackageElement;
119import javax.lang.model.element.QualifiedNameable;
120import javax.lang.model.element.TypeParameterElement;
121import javax.lang.model.element.VariableElement;
122import javax.lang.model.type.ArrayType;
123import javax.lang.model.type.ExecutableType;
124import javax.lang.model.type.TypeKind;
125import javax.lang.model.util.ElementFilter;
126import javax.lang.model.util.Types;
127import javax.tools.JavaFileManager.Location;
128import javax.tools.StandardLocation;
129
130import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo;
131import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
132import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
133import static jdk.jshell.TreeDissector.printType;
134
135import static java.util.stream.Collectors.joining;
136
137/**
138 * The concrete implementation of SourceCodeAnalysis.
139 * @author Robert Field
140 */
141class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
142
143    private static final Map<Path, ClassIndex> PATH_TO_INDEX = new HashMap<>();
144    private static final ExecutorService INDEXER = Executors.newFixedThreadPool(1, r -> {
145        Thread t = new Thread(r);
146        t.setDaemon(true);
147        t.setUncaughtExceptionHandler((thread, ex) -> ex.printStackTrace());
148        return t;
149    });
150
151    private final JShell proc;
152    private final CompletenessAnalyzer ca;
153    private final List<AutoCloseable> closeables = new ArrayList<>();
154    private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
155    private int indexVersion;
156    private int classpathVersion;
157    private final Object suspendLock = new Object();
158    private int suspend;
159
160    SourceCodeAnalysisImpl(JShell proc) {
161        this.proc = proc;
162        this.ca = new CompletenessAnalyzer(proc);
163
164        int cpVersion = classpathVersion = 1;
165
166        INDEXER.submit(() -> refreshIndexes(cpVersion));
167    }
168
169    @Override
170    public CompletionInfo analyzeCompletion(String srcInput) {
171        MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false);
172        if (mcm.endsWithOpenComment()) {
173            proc.debug(DBG_COMPA, "Incomplete (open comment): %s\n", srcInput);
174            return new CompletionInfoImpl(DEFINITELY_INCOMPLETE, null, srcInput + '\n');
175        }
176        String cleared = mcm.cleared();
177        String trimmedInput = Util.trimEnd(cleared);
178        if (trimmedInput.isEmpty()) {
179            // Just comment or empty
180            return new CompletionInfoImpl(Completeness.EMPTY, srcInput, "");
181        }
182        CaInfo info = ca.scan(trimmedInput);
183        Completeness status = info.status;
184        int unitEndPos = info.unitEndPos;
185        if (unitEndPos > srcInput.length()) {
186            unitEndPos = srcInput.length();
187        }
188        int nonCommentNonWhiteLength = trimmedInput.length();
189        String src = srcInput.substring(0, unitEndPos);
190        switch (status) {
191            case COMPLETE:
192                if (unitEndPos == nonCommentNonWhiteLength) {
193                    // The unit is the whole non-coment/white input plus semicolon
194                    String compileSource = src
195                            + mcm.mask().substring(nonCommentNonWhiteLength);
196                    proc.debug(DBG_COMPA, "Complete: %s\n", compileSource);
197                    proc.debug(DBG_COMPA, "   nothing remains.\n");
198                    return new CompletionInfoImpl(status, compileSource, "");
199                } else {
200                    String remain = srcInput.substring(unitEndPos);
201                    proc.debug(DBG_COMPA, "Complete: %s\n", src);
202                    proc.debug(DBG_COMPA, "          remaining: %s\n", remain);
203                    return new CompletionInfoImpl(status, src, remain);
204                }
205            case COMPLETE_WITH_SEMI:
206                // The unit is the whole non-coment/white input plus semicolon
207                String compileSource = src
208                        + ";"
209                        + mcm.mask().substring(nonCommentNonWhiteLength);
210                proc.debug(DBG_COMPA, "Complete with semi: %s\n", compileSource);
211                proc.debug(DBG_COMPA, "   nothing remains.\n");
212                return new CompletionInfoImpl(status, compileSource, "");
213            case DEFINITELY_INCOMPLETE:
214                proc.debug(DBG_COMPA, "Incomplete: %s\n", srcInput);
215                return new CompletionInfoImpl(status, null, srcInput + '\n');
216            case CONSIDERED_INCOMPLETE:
217                proc.debug(DBG_COMPA, "Considered incomplete: %s\n", srcInput);
218                return new CompletionInfoImpl(status, null, srcInput + '\n');
219            case EMPTY:
220                proc.debug(DBG_COMPA, "Detected empty: %s\n", srcInput);
221                return new CompletionInfoImpl(status, srcInput, "");
222            case UNKNOWN:
223                proc.debug(DBG_COMPA, "Detected error: %s\n", srcInput);
224                return new CompletionInfoImpl(status, srcInput, "");
225        }
226        throw new InternalError();
227    }
228
229    private Tree.Kind guessKind(String code) {
230        ParseTask pt = proc.taskFactory.parse(code);
231        List<? extends Tree> units = pt.units();
232        if (units.isEmpty()) {
233            return Tree.Kind.BLOCK;
234        }
235        Tree unitTree = units.get(0);
236        proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
237        return unitTree.getKind();
238    }
239
240    //TODO: would be better handled through a lexer:
241    private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
242
243    @Override
244    public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) {
245        suspendIndexing();
246        try {
247            return completionSuggestionsImpl(code, cursor, anchor);
248        } finally {
249            resumeIndexing();
250        }
251    }
252
253    private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[] anchor) {
254        code = code.substring(0, cursor);
255        Matcher m = JAVA_IDENTIFIER.matcher(code);
256        String identifier = "";
257        while (m.find()) {
258            if (m.end() == code.length()) {
259                cursor = m.start();
260                code = code.substring(0, cursor);
261                identifier = m.group();
262            }
263        }
264        code = code.substring(0, cursor);
265        if (code.trim().isEmpty()) { //TODO: comment handling
266            code += ";";
267        }
268        OuterWrap codeWrap;
269        switch (guessKind(code)) {
270            case IMPORT:
271                codeWrap = proc.outerMap.wrapImport(Wrap.simpleWrap(code + "any.any"), null);
272                break;
273            case CLASS:
274            case METHOD:
275                codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code));
276                break;
277            default:
278                codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
279                break;
280        }
281        String requiredPrefix = identifier;
282        return computeSuggestions(codeWrap, cursor, anchor).stream()
283                .filter(s -> s.continuation().startsWith(requiredPrefix) && !s.continuation().equals(REPL_DOESNOTMATTER_CLASS_NAME))
284                .sorted(Comparator.comparing(Suggestion::continuation))
285                .collect(collectingAndThen(toList(), Collections::unmodifiableList));
286    }
287
288    private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) {
289        AnalyzeTask at = proc.taskFactory.new AnalyzeTask(code);
290        SourcePositions sp = at.trees().getSourcePositions();
291        CompilationUnitTree topLevel = at.firstCuTree();
292        List<Suggestion> result = new ArrayList<>();
293        TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor));
294        if (tp != null) {
295            Scope scope = at.trees().getScope(tp);
296            Predicate<Element> accessibility = createAccessibilityFilter(at, tp);
297            Predicate<Element> smartTypeFilter;
298            Predicate<Element> smartFilter;
299            Iterable<TypeMirror> targetTypes = findTargetType(at, tp);
300            if (targetTypes != null) {
301                smartTypeFilter = el -> {
302                    TypeMirror resultOf = resultTypeOf(el);
303                    return Util.stream(targetTypes)
304                            .anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType));
305                };
306
307                smartFilter = IS_CLASS.negate()
308                                      .and(IS_INTERFACE.negate())
309                                      .and(IS_PACKAGE.negate())
310                                      .and(smartTypeFilter);
311            } else {
312                smartFilter = TRUE;
313                smartTypeFilter = TRUE;
314            }
315            switch (tp.getLeaf().getKind()) {
316                case MEMBER_SELECT: {
317                    MemberSelectTree mst = (MemberSelectTree)tp.getLeaf();
318                    if (mst.getIdentifier().contentEquals("*"))
319                        break;
320                    TreePath exprPath = new TreePath(tp, mst.getExpression());
321                    TypeMirror site = at.trees().getTypeMirror(exprPath);
322                    boolean staticOnly = isStaticContext(at, exprPath);
323                    ImportTree it = findImport(tp);
324                    boolean isImport = it != null;
325
326                    List<? extends Element> members = membersOf(at, site, staticOnly && !isImport);
327                    Predicate<Element> filter = accessibility;
328                    Function<Boolean, String> paren = DEFAULT_PAREN;
329
330                    if (isNewClass(tp)) { // new xxx.|
331                        Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR)
332                            .and(el -> {
333                                if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) {
334                                    return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC);
335                                }
336                                return true;
337                            });
338                        addElements(membersOf(at, members), constructorFilter, smartFilter, result);
339
340                        filter = filter.and(IS_PACKAGE);
341                    } else if (isThrowsClause(tp)) {
342                        staticOnly = true;
343                        filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
344                        smartFilter = IS_PACKAGE.negate().and(smartTypeFilter);
345                    } else if (isImport) {
346                        paren = NO_PAREN;
347                        if (!it.isStatic()) {
348                            filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
349                        }
350                    } else {
351                        filter = filter.and(IS_CONSTRUCTOR.negate());
352                    }
353
354                    filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY);
355
356                    addElements(members, filter, smartFilter, paren, result);
357                    break;
358                }
359                case IDENTIFIER:
360                    if (isNewClass(tp)) {
361                        Function<Element, Iterable<? extends Element>> listEnclosed =
362                                el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el)
363                                                                          : el.getEnclosedElements();
364                        Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE));
365                        NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf();
366                        ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression();
367                        if (enclosingExpression != null) { // expr.new IDENT|
368                            TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression));
369                            filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC));
370                            addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result);
371                        } else {
372                            addScopeElements(at, scope, listEnclosed, filter, smartFilter, result);
373                        }
374                        break;
375                    }
376                    if (isThrowsClause(tp)) {
377                        Predicate<Element> accept = accessibility.and(STATIC_ONLY)
378                                .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
379                        addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result);
380                        break;
381                    }
382                    ImportTree it = findImport(tp);
383                    if (it != null) {
384                        // the context of the identifier is an import, look for
385                        // package names that start with the identifier.
386                        // If and when Java allows imports from the default
387                        // package to the the default package which would allow
388                        // JShell to change to use the default package, and that
389                        // change is done, then this should use some variation
390                        // of membersOf(at, at.getElements().getPackageElement("").asType(), false)
391                        addElements(listPackages(at, ""),
392                                it.isStatic()
393                                        ? STATIC_ONLY.and(accessibility)
394                                        : accessibility,
395                                smartFilter, result);
396                    }
397                    break;
398                case CLASS: {
399                    Predicate<Element> accept = accessibility.and(IS_TYPE);
400                    addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
401                    addElements(primitivesOrVoid(at), TRUE, smartFilter, result);
402                    break;
403                }
404                case BLOCK:
405                case EMPTY_STATEMENT:
406                case ERRONEOUS: {
407                    boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv());
408                    Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE);
409                    if (isClass(tp)) {
410                        ClassTree clazz = (ClassTree) tp.getParentPath().getLeaf();
411                        if (clazz.getExtendsClause() == tp.getLeaf()) {
412                            accept = accept.and(IS_TYPE);
413                            smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.CLASS);
414                        } else {
415                            Predicate<Element> f = smartFilterFromList(at, tp, clazz.getImplementsClause(), tp.getLeaf());
416                            if (f != null) {
417                                accept = accept.and(IS_TYPE);
418                                smartFilter = f.and(el -> el.getKind() == ElementKind.INTERFACE);
419                            }
420                        }
421                    } else if (isTypeParameter(tp)) {
422                        TypeParameterTree tpt = (TypeParameterTree) tp.getParentPath().getLeaf();
423                        Predicate<Element> f = smartFilterFromList(at, tp, tpt.getBounds(), tp.getLeaf());
424                        if (f != null) {
425                            accept = accept.and(IS_TYPE);
426                            smartFilter = f;
427                            if (!tpt.getBounds().isEmpty() && tpt.getBounds().get(0) != tp.getLeaf()) {
428                                smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.INTERFACE);
429                            }
430                        }
431                    } else if (isVariable(tp)) {
432                        VariableTree var = (VariableTree) tp.getParentPath().getLeaf();
433                        if (var.getType() == tp.getLeaf()) {
434                            accept = accept.and(IS_TYPE);
435                        }
436                    }
437
438                    addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
439
440                    Tree parent = tp.getParentPath().getLeaf();
441                    switch (parent.getKind()) {
442                        case VARIABLE:
443                            accept = ((VariableTree)parent).getType() == tp.getLeaf() ?
444                                    IS_VOID.negate() :
445                                    TRUE;
446                            break;
447                        case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types
448                        case TYPE_PARAMETER:
449                        case CLASS:
450                        case INTERFACE:
451                        case ENUM:
452                            accept = FALSE;
453                            break;
454                        default:
455                            accept = TRUE;
456                            break;
457                    }
458                    addElements(primitivesOrVoid(at), accept, smartFilter, result);
459                    break;
460                }
461            }
462        }
463        anchor[0] = cursor;
464        return result;
465    }
466
467    private static final Set<Kind> CLASS_KINDS = EnumSet.of(
468            Kind.ANNOTATION_TYPE, Kind.CLASS, Kind.ENUM, Kind.INTERFACE
469    );
470
471    private Predicate<Element> smartFilterFromList(AnalyzeTask at, TreePath base, Collection<? extends Tree> types, Tree current) {
472        Set<Element> existingEls = new HashSet<>();
473
474        for (Tree type : types) {
475            if (type == current) {
476                return el -> !existingEls.contains(el);
477            }
478            existingEls.add(at.trees().getElement(new TreePath(base, type)));
479        }
480
481        return null;
482    }
483
484    @Override
485    public SnippetWrapper wrapper(Snippet snippet) {
486        return new SnippetWrapper() {
487            @Override
488            public String source() {
489                return snippet.source();
490            }
491
492            @Override
493            public String wrapped() {
494                return snippet.outerWrap().wrapped();
495            }
496
497            @Override
498            public String fullClassName() {
499                return snippet.classFullName();
500            }
501
502            @Override
503            public Snippet.Kind kind() {
504                return snippet.kind() == Snippet.Kind.ERRONEOUS
505                        ? ((ErroneousSnippet) snippet).probableKind()
506                        : snippet.kind();
507            }
508
509            @Override
510            public int sourceToWrappedPosition(int pos) {
511                return snippet.outerWrap().snippetIndexToWrapIndex(pos);
512            }
513
514            @Override
515            public int wrappedToSourcePosition(int pos) {
516                return snippet.outerWrap().wrapIndexToSnippetIndex(pos);
517            }
518        };
519    }
520
521    @Override
522    public List<SnippetWrapper> wrappers(String input) {
523        return proc.eval.sourceToSnippetsWithWrappers(input).stream()
524                .map(this::wrapper)
525                .collect(toList());
526    }
527
528    @Override
529    public Collection<Snippet> dependents(Snippet snippet) {
530        return proc.maps.getDependents(snippet);
531    }
532
533    private boolean isStaticContext(AnalyzeTask at, TreePath path) {
534        switch (path.getLeaf().getKind()) {
535            case ARRAY_TYPE:
536            case PRIMITIVE_TYPE:
537                return true;
538            default:
539                Element selectEl = at.trees().getElement(path);
540                return selectEl != null && (selectEl.getKind().isClass() || selectEl.getKind().isInterface() || selectEl.getKind() == ElementKind.TYPE_PARAMETER) && selectEl.asType().getKind() != TypeKind.ERROR;
541        }
542    }
543
544    private TreePath pathFor(CompilationUnitTree topLevel, SourcePositions sp, int pos) {
545        TreePath[] deepest = new TreePath[1];
546
547        new TreePathScanner<Void, Void>() {
548            @Override
549            public Void scan(Tree tree, Void p) {
550                if (tree == null)
551                    return null;
552
553                long start = sp.getStartPosition(topLevel, tree);
554                long end = sp.getEndPosition(topLevel, tree);
555                long prevEnd = deepest[0] != null ? sp.getEndPosition(topLevel, deepest[0].getLeaf()) : -1;
556
557                if (start <= pos && pos <= end &&
558                    (start != end || prevEnd != end || deepest[0] == null ||
559                     deepest[0].getParentPath().getLeaf() != getCurrentPath().getLeaf())) {
560                    deepest[0] = new TreePath(getCurrentPath(), tree);
561                    return super.scan(tree, p);
562                }
563
564                return null;
565            }
566            @Override
567            public Void visitErroneous(ErroneousTree node, Void p) {
568                return scan(node.getErrorTrees(), null);
569            }
570        }.scan(topLevel, null);
571
572        return deepest[0];
573    }
574
575    private boolean isNewClass(TreePath tp) {
576        return tp.getParentPath() != null &&
577               tp.getParentPath().getLeaf().getKind() == Kind.NEW_CLASS &&
578               ((NewClassTree) tp.getParentPath().getLeaf()).getIdentifier() == tp.getLeaf();
579    }
580
581    private boolean isThrowsClause(TreePath tp) {
582        Tree parent = tp.getParentPath().getLeaf();
583        return parent.getKind() == Kind.METHOD &&
584                ((MethodTree)parent).getThrows().contains(tp.getLeaf());
585    }
586
587    private boolean isClass(TreePath tp) {
588        return tp.getParentPath() != null &&
589               CLASS_KINDS.contains(tp.getParentPath().getLeaf().getKind());
590    }
591
592    private boolean isTypeParameter(TreePath tp) {
593        return tp.getParentPath() != null &&
594               tp.getParentPath().getLeaf().getKind() == Kind.TYPE_PARAMETER;
595    }
596
597    private boolean isVariable(TreePath tp) {
598        return tp.getParentPath() != null &&
599               tp.getParentPath().getLeaf().getKind() == Kind.VARIABLE;
600    }
601
602    private ImportTree findImport(TreePath tp) {
603        while (tp != null && tp.getLeaf().getKind() != Kind.IMPORT) {
604            tp = tp.getParentPath();
605        }
606        return tp != null ? (ImportTree)tp.getLeaf() : null;
607    }
608
609    private Predicate<Element> createAccessibilityFilter(AnalyzeTask at, TreePath tp) {
610        Scope scope = at.trees().getScope(tp);
611        return el -> {
612            switch (el.getKind()) {
613                case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE:
614                    return at.trees().isAccessible(scope, (TypeElement) el);
615                case PACKAGE:
616                case EXCEPTION_PARAMETER: case PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE:
617                    return true;
618                default:
619                    TypeMirror type = el.getEnclosingElement().asType();
620                    if (type.getKind() == TypeKind.DECLARED)
621                        return at.trees().isAccessible(scope, el, (DeclaredType) type);
622                    else
623                        return true;
624            }
625        };
626    }
627
628    private final Predicate<Element> TRUE = el -> true;
629    private final Predicate<Element> FALSE = TRUE.negate();
630    private final Predicate<Element> IS_STATIC = el -> el.getModifiers().contains(Modifier.STATIC);
631    private final Predicate<Element> IS_CONSTRUCTOR = el -> el.getKind() == ElementKind.CONSTRUCTOR;
632    private final Predicate<Element> IS_METHOD = el -> el.getKind() == ElementKind.METHOD;
633    private final Predicate<Element> IS_PACKAGE = el -> el.getKind() == ElementKind.PACKAGE;
634    private final Predicate<Element> IS_CLASS = el -> el.getKind().isClass();
635    private final Predicate<Element> IS_INTERFACE = el -> el.getKind().isInterface();
636    private final Predicate<Element> IS_TYPE = IS_CLASS.or(IS_INTERFACE).or(el -> el.getKind() == ElementKind.TYPE_PARAMETER);
637    private final Predicate<Element> IS_VOID = el -> el.asType().getKind() == TypeKind.VOID;
638    private final Predicate<Element> STATIC_ONLY = el -> {
639        ElementKind kind = el.getKind();
640        Element encl = el.getEnclosingElement();
641        ElementKind enclKind = encl != null ? encl.getKind() : ElementKind.OTHER;
642
643        return IS_STATIC.or(IS_PACKAGE).or(IS_CLASS).or(IS_INTERFACE).test(el) || IS_PACKAGE.test(encl) ||
644                (kind == ElementKind.TYPE_PARAMETER && !enclKind.isClass() && !enclKind.isInterface());
645    };
646    private final Predicate<Element> INSTANCE_ONLY = el -> {
647        Element encl = el.getEnclosingElement();
648
649        return IS_STATIC.or(IS_CLASS).or(IS_INTERFACE).negate().test(el) ||
650                IS_PACKAGE.test(encl);
651    };
652    private final Function<Element, Iterable<? extends Element>> IDENTITY = Collections::singletonList;
653    private final Function<Boolean, String> DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()";
654    private final Function<Boolean, String> NO_PAREN = hasParams -> "";
655
656    private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, List<Suggestion> result) {
657        addElements(elements, accept, smart, DEFAULT_PAREN, result);
658    }
659    private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, Function<Boolean, String> paren, List<Suggestion> result) {
660        Set<String> hasParams = Util.stream(elements)
661                .filter(accept)
662                .filter(IS_CONSTRUCTOR.or(IS_METHOD))
663                .filter(c -> !((ExecutableElement)c).getParameters().isEmpty())
664                .map(this::simpleName)
665                .collect(toSet());
666
667        for (Element c : elements) {
668            if (!accept.test(c))
669                continue;
670            if (c.getKind() == ElementKind.METHOD &&
671                c.getSimpleName().contentEquals(Util.DOIT_METHOD_NAME) &&
672                ((ExecutableElement) c).getParameters().isEmpty()) {
673                continue;
674            }
675            String simpleName = simpleName(c);
676            if (c.getKind() == ElementKind.CONSTRUCTOR || c.getKind() == ElementKind.METHOD) {
677                simpleName += paren.apply(hasParams.contains(simpleName));
678            }
679            result.add(new SuggestionImpl(simpleName, smart.test(c)));
680        }
681    }
682
683    private String simpleName(Element el) {
684        return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString()
685                                                       : el.getSimpleName().toString();
686    }
687
688    private List<? extends Element> membersOf(AnalyzeTask at, TypeMirror site, boolean shouldGenerateDotClassItem) {
689        if (site  == null)
690            return Collections.emptyList();
691
692        switch (site.getKind()) {
693            case DECLARED: {
694                TypeElement element = (TypeElement) at.getTypes().asElement(site);
695                List<Element> result = new ArrayList<>();
696                result.addAll(at.getElements().getAllMembers(element));
697                if (shouldGenerateDotClassItem) {
698                    result.add(createDotClassSymbol(at, site));
699                }
700                result.removeIf(el -> el.getKind() == ElementKind.STATIC_INIT);
701                return result;
702            }
703            case ERROR: {
704                //try current qualified name as a package:
705                TypeElement typeElement = (TypeElement) at.getTypes().asElement(site);
706                Element enclosingElement = typeElement.getEnclosingElement();
707                String parentPackageName = enclosingElement instanceof QualifiedNameable ?
708                    ((QualifiedNameable)enclosingElement).getQualifiedName().toString() :
709                    "";
710                Set<PackageElement> packages = listPackages(at, parentPackageName);
711                return packages.stream()
712                               .filter(p -> p.getQualifiedName().equals(typeElement.getQualifiedName()))
713                               .findAny()
714                               .map(p -> membersOf(at, p.asType(), false))
715                               .orElse(Collections.emptyList());
716            }
717            case PACKAGE: {
718                String packageName = site.toString()/*XXX*/;
719                List<Element> result = new ArrayList<>();
720                result.addAll(getEnclosedElements(at.getElements().getPackageElement(packageName)));
721                result.addAll(listPackages(at, packageName));
722                return result;
723            }
724            case BOOLEAN: case BYTE: case SHORT: case CHAR:
725            case INT: case FLOAT: case LONG: case DOUBLE:
726            case VOID: {
727                return shouldGenerateDotClassItem ?
728                    Collections.singletonList(createDotClassSymbol(at, site)) :
729                    Collections.emptyList();
730            }
731            case ARRAY: {
732                List<Element> result = new ArrayList<>();
733                result.add(createArrayLengthSymbol(at, site));
734                if (shouldGenerateDotClassItem)
735                    result.add(createDotClassSymbol(at, site));
736                return result;
737            }
738            default:
739                return Collections.emptyList();
740        }
741    }
742
743    private List<? extends Element> membersOf(AnalyzeTask at, List<? extends Element> elements) {
744        return elements.stream()
745                .flatMap(e -> membersOf(at, e.asType(), true).stream())
746                .collect(toList());
747    }
748
749    private List<? extends Element> getEnclosedElements(PackageElement packageEl) {
750        if (packageEl == null) {
751            return Collections.emptyList();
752        }
753        //workaround for: JDK-8024687
754        while (true) {
755            try {
756                return packageEl.getEnclosedElements()
757                                .stream()
758                                .filter(el -> el.asType() != null)
759                                .filter(el -> el.asType().getKind() != TypeKind.ERROR)
760                                .collect(toList());
761            } catch (CompletionFailure cf) {
762                //ignore...
763            }
764        }
765    }
766
767    private List<? extends Element> primitivesOrVoid(AnalyzeTask at) {
768        Types types = at.getTypes();
769        return Stream.of(
770                TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR,
771                TypeKind.DOUBLE, TypeKind.FLOAT, TypeKind.INT,
772                TypeKind.LONG, TypeKind.SHORT, TypeKind.VOID)
773                .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk)))
774                .map(Type::asElement)
775                .collect(toList());
776    }
777
778    void classpathChanged() {
779        synchronized (currentIndexes) {
780            int cpVersion = ++classpathVersion;
781
782            INDEXER.submit(() -> refreshIndexes(cpVersion));
783        }
784    }
785
786    private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) {
787        synchronized (currentIndexes) {
788            return currentIndexes.values()
789                                 .stream()
790                                 .flatMap(idx -> idx.packages.stream())
791                                 .filter(p -> enclosingPackage.isEmpty() || p.startsWith(enclosingPackage + "."))
792                                 .map(p -> {
793                                     int dot = p.indexOf('.', enclosingPackage.length() + 1);
794                                     return dot == (-1) ? p : p.substring(0, dot);
795                                 })
796                                 .distinct()
797                                 .map(p -> createPackageElement(at, p))
798                                 .collect(Collectors.toSet());
799        }
800    }
801
802    private PackageElement createPackageElement(AnalyzeTask at, String packageName) {
803        Names names = Names.instance(at.getContext());
804        Symtab syms = Symtab.instance(at.getContext());
805        PackageElement existing = syms.enterPackage(syms.unnamedModule, names.fromString(packageName));
806
807        return existing;
808    }
809
810    private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) {
811        Name length = Names.instance(at.getContext()).length;
812        Type intType = Symtab.instance(at.getContext()).intType;
813
814        return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym);
815    }
816
817    private Element createDotClassSymbol(AnalyzeTask at, TypeMirror site) {
818        Name _class = Names.instance(at.getContext())._class;
819        Type classType = Symtab.instance(at.getContext()).classType;
820        Type erasedSite = (Type)at.getTypes().erasure(site);
821        classType = new ClassType(classType.getEnclosingType(), com.sun.tools.javac.util.List.of(erasedSite), classType.asElement());
822
823        return new VarSymbol(Flags.PUBLIC | Flags.STATIC | Flags.FINAL, _class, classType, erasedSite.tsym);
824    }
825
826    private Iterable<? extends Element> scopeContent(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor) {
827        Iterable<Scope> scopeIterable = () -> new Iterator<Scope>() {
828            private Scope currentScope = scope;
829            @Override
830            public boolean hasNext() {
831                return currentScope != null;
832            }
833            @Override
834            public Scope next() {
835                if (!hasNext())
836                    throw new NoSuchElementException();
837                try {
838                    return currentScope;
839                } finally {
840                    currentScope = currentScope.getEnclosingScope();
841                }
842            }
843        };
844        @SuppressWarnings("unchecked")
845        List<Element> result = Util.stream(scopeIterable)
846                             .flatMap(this::localElements)
847                             .flatMap(el -> Util.stream((Iterable<Element>)elementConvertor.apply(el)))
848                             .collect(toCollection(ArrayList :: new));
849        result.addAll(listPackages(at, ""));
850        return result;
851    }
852
853    private Stream<Element> localElements(Scope scope) {
854        //workaround for: JDK-8024687
855        Iterable<Element> elementsIt = () -> new Iterator<Element>() {
856            Iterator<? extends Element> it = scope.getLocalElements().iterator();
857            @Override
858            public boolean hasNext() {
859                while (true) {
860                    try {
861                        return it.hasNext();
862                    } catch (CompletionFailure cf) {
863                        //ignore...
864                    }
865                }
866            }
867            @Override
868            public Element next() {
869                while (true) {
870                    try {
871                        return it.next();
872                    } catch (CompletionFailure cf) {
873                        //ignore...
874                    }
875                }
876            }
877        };
878        Stream<Element> elements = Util.stream(elementsIt);
879
880        if (scope.getEnclosingScope() != null &&
881            scope.getEnclosingClass() != scope.getEnclosingScope().getEnclosingClass()) {
882            elements = Stream.concat(elements, scope.getEnclosingClass().getEnclosedElements().stream());
883        }
884
885        return elements;
886    }
887
888    @SuppressWarnings("fallthrough")
889    private Iterable<TypeMirror> findTargetType(AnalyzeTask at, TreePath forPath) {
890        if (forPath.getParentPath() == null)
891            return null;
892
893        Tree current = forPath.getLeaf();
894
895        switch (forPath.getParentPath().getLeaf().getKind()) {
896            case ASSIGNMENT: {
897                AssignmentTree tree = (AssignmentTree) forPath.getParentPath().getLeaf();
898                if (tree.getExpression() == current)
899                    return Collections.singletonList(at.trees().getTypeMirror(new TreePath(forPath.getParentPath(), tree.getVariable())));
900                break;
901            }
902            case VARIABLE: {
903                VariableTree tree = (VariableTree) forPath.getParentPath().getLeaf();
904                if (tree.getInitializer()== current)
905                    return Collections.singletonList(at.trees().getTypeMirror(forPath.getParentPath()));
906                break;
907            }
908            case ERRONEOUS:
909                return findTargetType(at, forPath.getParentPath());
910            case NEW_CLASS: {
911                NewClassTree nct = (NewClassTree) forPath.getParentPath().getLeaf();
912                List<TypeMirror> actuals = computeActualInvocationTypes(at, nct.getArguments(), forPath);
913
914                if (actuals != null) {
915                    Iterable<Pair<ExecutableElement, ExecutableType>> candidateConstructors = newClassCandidates(at, forPath.getParentPath());
916
917                    return computeSmartTypesForExecutableType(at, candidateConstructors, actuals);
918                } else {
919                    return findTargetType(at, forPath.getParentPath());
920                }
921            }
922            case METHOD:
923                if (!isThrowsClause(forPath)) {
924                    break;
925                }
926                // fall through
927            case THROW:
928                return Collections.singletonList(at.getElements().getTypeElement("java.lang.Throwable").asType());
929            case METHOD_INVOCATION: {
930                MethodInvocationTree mit = (MethodInvocationTree) forPath.getParentPath().getLeaf();
931                List<TypeMirror> actuals = computeActualInvocationTypes(at, mit.getArguments(), forPath);
932
933                if (actuals == null)
934                    return null;
935
936                Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods = methodCandidates(at, forPath.getParentPath());
937
938                return computeSmartTypesForExecutableType(at, candidateMethods, actuals);
939            }
940        }
941
942        return null;
943    }
944
945    private List<TypeMirror> computeActualInvocationTypes(AnalyzeTask at, List<? extends ExpressionTree> arguments, TreePath currentArgument) {
946        if (currentArgument == null)
947            return null;
948
949        int paramIndex = arguments.indexOf(currentArgument.getLeaf());
950
951        if (paramIndex == (-1))
952            return null;
953
954        List<TypeMirror> actuals = new ArrayList<>();
955
956        for (ExpressionTree arg : arguments.subList(0, paramIndex)) {
957            actuals.add(at.trees().getTypeMirror(new TreePath(currentArgument.getParentPath(), arg)));
958        }
959
960        return actuals;
961    }
962
963    private List<Pair<ExecutableElement, ExecutableType>> filterExecutableTypesByArguments(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) {
964        List<Pair<ExecutableElement, ExecutableType>> candidate = new ArrayList<>();
965        int paramIndex = precedingActualTypes.size();
966
967        OUTER:
968        for (Pair<ExecutableElement, ExecutableType> method : candidateMethods) {
969            boolean varargInvocation = paramIndex >= method.snd.getParameterTypes().size();
970
971            for (int i = 0; i < paramIndex; i++) {
972                TypeMirror actual = precedingActualTypes.get(i);
973
974                if (this.parameterType(method.fst, method.snd, i, !varargInvocation)
975                        .noneMatch(formal -> at.getTypes().isAssignable(actual, formal))) {
976                    continue OUTER;
977                }
978            }
979            candidate.add(method);
980        }
981
982        return candidate;
983    }
984
985    private Stream<TypeMirror> parameterType(ExecutableElement method, ExecutableType methodType, int paramIndex, boolean allowVarArgsArray) {
986        int paramCount = methodType.getParameterTypes().size();
987        if (paramIndex >= paramCount && !method.isVarArgs())
988            return Stream.empty();
989        if (paramIndex < paramCount - 1 || !method.isVarArgs())
990            return Stream.of(methodType.getParameterTypes().get(paramIndex));
991        TypeMirror varargType = methodType.getParameterTypes().get(paramCount - 1);
992        TypeMirror elemenType = ((ArrayType) varargType).getComponentType();
993        if (paramIndex >= paramCount || !allowVarArgsArray)
994            return Stream.of(elemenType);
995        return Stream.of(varargType, elemenType);
996    }
997
998    private List<TypeMirror> computeSmartTypesForExecutableType(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) {
999        List<TypeMirror> candidate = new ArrayList<>();
1000        int paramIndex = precedingActualTypes.size();
1001
1002        this.filterExecutableTypesByArguments(at, candidateMethods, precedingActualTypes)
1003            .stream()
1004            .flatMap(method -> parameterType(method.fst, method.snd, paramIndex, true))
1005            .forEach(candidate::add);
1006
1007        return candidate;
1008    }
1009
1010
1011    private TypeMirror resultTypeOf(Element el) {
1012        //TODO: should reflect the type of site!
1013        switch (el.getKind()) {
1014            case METHOD:
1015                return ((ExecutableElement) el).getReturnType();
1016            case CONSTRUCTOR:
1017            case INSTANCE_INIT: case STATIC_INIT: //TODO: should be filtered out
1018                return el.getEnclosingElement().asType();
1019            default:
1020                return el.asType();
1021        }
1022    }
1023
1024    private void addScopeElements(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor, Predicate<Element> filter, Predicate<Element> smartFilter, List<Suggestion> result) {
1025        addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result);
1026    }
1027
1028    private Iterable<Pair<ExecutableElement, ExecutableType>> methodCandidates(AnalyzeTask at, TreePath invocation) {
1029        MethodInvocationTree mit = (MethodInvocationTree) invocation.getLeaf();
1030        ExpressionTree select = mit.getMethodSelect();
1031        List<Pair<ExecutableElement, ExecutableType>> result = new ArrayList<>();
1032        Predicate<Element> accessibility = createAccessibilityFilter(at, invocation);
1033
1034        switch (select.getKind()) {
1035            case MEMBER_SELECT:
1036                MemberSelectTree mst = (MemberSelectTree) select;
1037                TreePath tp = new TreePath(new TreePath(invocation, select), mst.getExpression());
1038                TypeMirror site = at.trees().getTypeMirror(tp);
1039
1040                if (site == null || site.getKind() != TypeKind.DECLARED)
1041                    break;
1042
1043                Element siteEl = at.getTypes().asElement(site);
1044
1045                if (siteEl == null)
1046                    break;
1047
1048                if (isStaticContext(at, tp)) {
1049                    accessibility = accessibility.and(STATIC_ONLY);
1050                }
1051
1052                for (ExecutableElement ee : ElementFilter.methodsIn(membersOf(at, siteEl.asType(), false))) {
1053                    if (ee.getSimpleName().contentEquals(mst.getIdentifier())) {
1054                        if (accessibility.test(ee)) {
1055                            result.add(Pair.of(ee, (ExecutableType) at.getTypes().asMemberOf((DeclaredType) site, ee)));
1056                        }
1057                    }
1058                }
1059                break;
1060            case IDENTIFIER:
1061                IdentifierTree it = (IdentifierTree) select;
1062                for (ExecutableElement ee : ElementFilter.methodsIn(scopeContent(at, at.trees().getScope(invocation), IDENTITY))) {
1063                    if (ee.getSimpleName().contentEquals(it.getName())) {
1064                        if (accessibility.test(ee)) {
1065                            result.add(Pair.of(ee, (ExecutableType) ee.asType())); //XXX: proper site
1066                        }
1067                    }
1068                }
1069                break;
1070            default:
1071                break;
1072        }
1073
1074        return result;
1075    }
1076
1077    private Iterable<Pair<ExecutableElement, ExecutableType>> newClassCandidates(AnalyzeTask at, TreePath newClassPath) {
1078        NewClassTree nct = (NewClassTree) newClassPath.getLeaf();
1079        Element type = at.trees().getElement(new TreePath(newClassPath.getParentPath(), nct.getIdentifier()));
1080        TypeMirror targetType = at.trees().getTypeMirror(newClassPath);
1081        if (targetType == null || targetType.getKind() != TypeKind.DECLARED) {
1082            Iterable<TypeMirror> targetTypes = findTargetType(at, newClassPath);
1083            if (targetTypes == null)
1084                targetTypes = Collections.emptyList();
1085            targetType =
1086                    StreamSupport.stream(targetTypes.spliterator(), false)
1087                                 .filter(t -> at.getTypes().asElement(t) == type)
1088                                 .findAny()
1089                                 .orElse(at.getTypes().erasure(type.asType()));
1090        }
1091        List<Pair<ExecutableElement, ExecutableType>> candidateConstructors = new ArrayList<>();
1092        Predicate<Element> accessibility = createAccessibilityFilter(at, newClassPath);
1093
1094        if (targetType != null &&
1095            targetType.getKind() == TypeKind.DECLARED &&
1096            type != null &&
1097            (type.getKind().isClass() || type.getKind().isInterface())) {
1098            for (ExecutableElement constr : ElementFilter.constructorsIn(type.getEnclosedElements())) {
1099                if (accessibility.test(constr)) {
1100                    ExecutableType constrType =
1101                            (ExecutableType) at.getTypes().asMemberOf((DeclaredType) targetType, constr);
1102                    candidateConstructors.add(Pair.of(constr, constrType));
1103                }
1104            }
1105        }
1106
1107        return candidateConstructors;
1108    }
1109
1110    @Override
1111    public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) {
1112        suspendIndexing();
1113        try {
1114            return documentationImpl(code, cursor, computeJavadoc);
1115        } finally {
1116            resumeIndexing();
1117        }
1118    }
1119
1120    //tweaked by tests to disable reading parameter names from classfiles so that tests using
1121    //JDK's classes are stable for both release and fastdebug builds:
1122    private final String[] keepParameterNames = new String[] {
1123        "-parameters"
1124    };
1125
1126    private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
1127        code = code.substring(0, cursor);
1128        if (code.trim().isEmpty()) { //TODO: comment handling
1129            code += ";";
1130        }
1131
1132        if (guessKind(code) == Kind.IMPORT)
1133            return Collections.emptyList();
1134
1135        OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
1136        AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
1137        SourcePositions sp = at.trees().getSourcePositions();
1138        CompilationUnitTree topLevel = at.firstCuTree();
1139        TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
1140
1141        if (tp == null)
1142            return Collections.emptyList();
1143
1144        TreePath prevPath = null;
1145        while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION &&
1146               tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER &&
1147               tp.getLeaf().getKind() != Kind.MEMBER_SELECT) {
1148            prevPath = tp;
1149            tp = tp.getParentPath();
1150        }
1151
1152        if (tp == null)
1153            return Collections.emptyList();
1154
1155        Stream<Element> elements;
1156        Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
1157        List<? extends ExpressionTree> arguments;
1158
1159        if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
1160            if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
1161                MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
1162                candidates = methodCandidates(at, tp);
1163                arguments = mit.getArguments();
1164            } else {
1165                NewClassTree nct = (NewClassTree) tp.getLeaf();
1166                candidates = newClassCandidates(at, tp);
1167                arguments = nct.getArguments();
1168            }
1169
1170            if (!isEmptyArgumentsContext(arguments)) {
1171                List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
1172                List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
1173
1174                candidates =
1175                        this.filterExecutableTypesByArguments(at, candidates, fullActuals)
1176                            .stream()
1177                            .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
1178                            .collect(Collectors.toList());
1179            }
1180
1181            elements = Util.stream(candidates).map(method -> method.fst);
1182        } else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
1183            Element el = at.trees().getElement(tp);
1184
1185            if (el == null ||
1186                el.asType().getKind() == TypeKind.ERROR ||
1187                (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
1188                //erroneous element:
1189                return Collections.emptyList();
1190            }
1191
1192            elements = Stream.of(el);
1193        } else {
1194            return Collections.emptyList();
1195        }
1196
1197        List<Documentation> result = Collections.emptyList();
1198
1199        try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
1200            result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
1201                             .filter(Objects::nonNull)
1202                             .collect(Collectors.toList());
1203        } catch (IOException ex) {
1204            proc.debug(ex, "JavadocHelper.close()");
1205        }
1206
1207        return result;
1208    }
1209
1210    private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) {
1211        String javadoc = null;
1212        try {
1213            if (hasSyntheticParameterNames(el)) {
1214                el = helper.getSourceElement(el);
1215            }
1216            if (computeJavadoc) {
1217                javadoc = helper.getResolvedDocComment(el);
1218            }
1219        } catch (IOException ex) {
1220            proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
1221        }
1222        String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true));
1223        return new DocumentationImpl(signature,  javadoc);
1224    }
1225
1226    public void close() {
1227        for (AutoCloseable closeable : closeables) {
1228            try {
1229                closeable.close();
1230            } catch (Exception ex) {
1231                proc.debug(ex, "SourceCodeAnalysisImpl.close()");
1232            }
1233        }
1234    }
1235
1236    private static final class DocumentationImpl implements Documentation {
1237
1238        private final String signature;
1239        private final String javadoc;
1240
1241        public DocumentationImpl(String signature, String javadoc) {
1242            this.signature = signature;
1243            this.javadoc = javadoc;
1244        }
1245
1246        @Override
1247        public String signature() {
1248            return signature;
1249        }
1250
1251        @Override
1252        public String javadoc() {
1253            return javadoc;
1254        }
1255
1256    }
1257
1258    private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
1259        if (arguments.size() == 1) {
1260            Tree firstArgument = arguments.get(0);
1261            return firstArgument.getKind() == Kind.ERRONEOUS;
1262        }
1263        return false;
1264    }
1265
1266    private boolean hasSyntheticParameterNames(Element el) {
1267        if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
1268            return false;
1269
1270        ExecutableElement ee = (ExecutableElement) el;
1271
1272        if (ee.getParameters().isEmpty())
1273            return false;
1274
1275        return ee.getParameters()
1276                 .stream()
1277                 .allMatch(param -> param.getSimpleName().toString().startsWith("arg"));
1278    }
1279
1280    private static List<Path> availableSourcesOverride; //for tests
1281    private List<Path> availableSources;
1282
1283    private List<Path> findSources() {
1284        if (availableSources != null) {
1285            return availableSources;
1286        }
1287        if (availableSourcesOverride != null) {
1288            return availableSources = availableSourcesOverride;
1289        }
1290        List<Path> result = new ArrayList<>();
1291        Path home = Paths.get(System.getProperty("java.home"));
1292        Path srcZip = home.resolve("lib").resolve("src.zip");
1293        if (!Files.isReadable(srcZip))
1294            srcZip = home.getParent().resolve("src.zip");
1295        if (Files.isReadable(srcZip)) {
1296            boolean keepOpen = false;
1297            FileSystem zipFO = null;
1298
1299            try {
1300                URI uri = URI.create("jar:" + srcZip.toUri());
1301                zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap());
1302                Path root = zipFO.getRootDirectories().iterator().next();
1303
1304                if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
1305                    //non-modular format:
1306                    result.add(srcZip);
1307                } else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
1308                    //modular format:
1309                    try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
1310                        for (Path p : ds) {
1311                            if (Files.isDirectory(p)) {
1312                                result.add(p);
1313                            }
1314                        }
1315                    }
1316
1317                    keepOpen = true;
1318                }
1319            } catch (IOException ex) {
1320                proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
1321            } finally {
1322                if (zipFO != null) {
1323                    if (keepOpen) {
1324                        closeables.add(zipFO);
1325                    } else {
1326                        try {
1327                            zipFO.close();
1328                        } catch (IOException ex) {
1329                            proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
1330                        }
1331                    }
1332                }
1333            }
1334        }
1335        return availableSources = result;
1336    }
1337
1338    private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) {
1339        switch (el.getKind()) {
1340            case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
1341                TypeElement type = (TypeElement)el;
1342                String fullname = type.getQualifiedName().toString();
1343                Element pkg = at.getElements().getPackageOf(el);
1344                String name = pkg == null || useFQN ? fullname :
1345                        proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString());
1346
1347                return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames);
1348            }
1349            case TYPE_PARAMETER: {
1350                TypeParameterElement tp = (TypeParameterElement)el;
1351                String name = tp.getSimpleName().toString();
1352
1353                List<? extends TypeMirror> bounds = tp.getBounds();
1354                boolean boundIsObject = bounds.isEmpty() ||
1355                        bounds.size() == 1 && at.getTypes().isSameType(bounds.get(0), Symtab.instance(at.getContext()).objectType);
1356
1357                return boundIsObject
1358                        ? name
1359                        : name + " extends " + bounds.stream()
1360                                .map(bound -> printType(at, proc, bound))
1361                                .collect(joining(" & "));
1362            }
1363            case FIELD:
1364                return appendDot(elementHeader(at, el.getEnclosingElement(), includeParameterNames, false)) + el.getSimpleName() + ":" + el.asType();
1365            case ENUM_CONSTANT:
1366                return appendDot(elementHeader(at, el.getEnclosingElement(), includeParameterNames, false)) + el.getSimpleName();
1367            case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
1368                return el.getSimpleName() + ":" + el.asType();
1369            case CONSTRUCTOR: case METHOD: {
1370                StringBuilder header = new StringBuilder();
1371
1372                boolean isMethod = el.getKind() == ElementKind.METHOD;
1373                ExecutableElement method = (ExecutableElement) el;
1374
1375                if (isMethod) {
1376                    // return type
1377                    header.append(printType(at, proc, method.getReturnType())).append(" ");
1378                } else {
1379                    // type parameters for the constructor
1380                    String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames);
1381                    if (!typeParameters.isEmpty()) {
1382                        header.append(typeParameters).append(" ");
1383                    }
1384                }
1385
1386                // receiver type
1387                String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false);
1388                header.append(clazz);
1389
1390                if (isMethod) {
1391                    //method name with type parameters
1392                    (clazz.isEmpty() ? header : header.append("."))
1393                            .append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames))
1394                            .append(el.getSimpleName());
1395                }
1396
1397                // arguments
1398                header.append("(");
1399                String sep = "";
1400                for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) {
1401                    VariableElement p = i.next();
1402                    header.append(sep);
1403                    if (!i.hasNext() && method.isVarArgs()) {
1404                        header.append(printType(at, proc, unwrapArrayType(p.asType()))).append("...");
1405                    } else {
1406                        header.append(printType(at, proc, p.asType()));
1407                    }
1408                    if (includeParameterNames) {
1409                        header.append(" ");
1410                        header.append(p.getSimpleName());
1411                    }
1412                    sep = ", ";
1413                }
1414                header.append(")");
1415
1416                // throws
1417                List<? extends TypeMirror> thrownTypes = method.getThrownTypes();
1418                if (!thrownTypes.isEmpty()) {
1419                    header.append(" throws ")
1420                            .append(thrownTypes.stream()
1421                                    .map(type -> printType(at, proc, type))
1422                                    .collect(joining(", ")));
1423                }
1424                return header.toString();
1425            }
1426            default:
1427                return el.toString();
1428        }
1429    }
1430    private String appendDot(String fqn) {
1431        return fqn.isEmpty() ? fqn : fqn + ".";
1432    }
1433    private TypeMirror unwrapArrayType(TypeMirror arrayType) {
1434        if (arrayType.getKind() == TypeKind.ARRAY) {
1435            return ((ArrayType)arrayType).getComponentType();
1436        }
1437        return arrayType;
1438    }
1439    private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters, boolean includeParameterNames) {
1440        return typeParameters.isEmpty() ? ""
1441                : typeParameters.stream()
1442                        .map(tp -> elementHeader(at, tp, includeParameterNames, false))
1443                        .collect(joining(", ", "<", ">"));
1444    }
1445
1446    @Override
1447    public String analyzeType(String code, int cursor) {
1448        switch (guessKind(code)) {
1449            case IMPORT: case METHOD: case CLASS: case ENUM:
1450            case INTERFACE: case ANNOTATION_TYPE: case VARIABLE:
1451                return null;
1452            default:
1453                break;
1454        }
1455        ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(code, proc);
1456        return (ei == null || !ei.isNonVoid)
1457                ? null
1458                : ei.typeName;
1459    }
1460
1461    @Override
1462    public QualifiedNames listQualifiedNames(String code, int cursor) {
1463        code = code.substring(0, cursor);
1464        if (code.trim().isEmpty()) {
1465            return new QualifiedNames(Collections.emptyList(), -1, true, false);
1466        }
1467        OuterWrap codeWrap;
1468        switch (guessKind(code)) {
1469            case IMPORT:
1470                return new QualifiedNames(Collections.emptyList(), -1, true, false);
1471            case METHOD:
1472                codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code));
1473                break;
1474            default:
1475                codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
1476                break;
1477        }
1478        AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap);
1479        SourcePositions sp = at.trees().getSourcePositions();
1480        CompilationUnitTree topLevel = at.firstCuTree();
1481        TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length()));
1482        if (tp.getLeaf().getKind() != Kind.IDENTIFIER) {
1483            return new QualifiedNames(Collections.emptyList(), -1, true, false);
1484        }
1485        Scope scope = at.trees().getScope(tp);
1486        TypeMirror type = at.trees().getTypeMirror(tp);
1487        Element el = at.trees().getElement(tp);
1488
1489        boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) ||
1490                            (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty());
1491        String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString();
1492        boolean upToDate;
1493        List<String> result;
1494
1495        synchronized (currentIndexes) {
1496            upToDate = classpathVersion == indexVersion;
1497            result = currentIndexes.values()
1498                                   .stream()
1499                                   .flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName,
1500                                                                                        Collections.emptyList()).stream())
1501                                   .distinct()
1502                                   .filter(fqn -> isAccessible(at, scope, fqn))
1503                                   .sorted()
1504                                   .collect(Collectors.toList());
1505        }
1506
1507        return new QualifiedNames(result, simpleName.length(), upToDate, !erroneous);
1508    }
1509
1510    private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) {
1511        TypeElement type = at.getElements().getTypeElement(fqn);
1512        if (type == null)
1513            return false;
1514        return at.trees().isAccessible(scope, type);
1515    }
1516
1517    //--------------------
1518    // classpath indexing:
1519    //--------------------
1520
1521    //the indexing can be suspended when a more important task is running:
1522    private void waitIndexingNotSuspended() {
1523        boolean suspendedNotified = false;
1524        synchronized (suspendLock) {
1525            while (suspend > 0) {
1526                if (!suspendedNotified) {
1527                    suspendedNotified = true;
1528                }
1529                try {
1530                    suspendLock.wait();
1531                } catch (InterruptedException ex) {
1532                }
1533            }
1534        }
1535    }
1536
1537    public void suspendIndexing() {
1538        synchronized (suspendLock) {
1539            suspend++;
1540        }
1541    }
1542
1543    public void resumeIndexing() {
1544        synchronized (suspendLock) {
1545            if (--suspend == 0) {
1546                suspendLock.notifyAll();
1547            }
1548        }
1549    }
1550
1551    //update indexes, either initially or after a classpath change:
1552    private void refreshIndexes(int version) {
1553        try {
1554            Collection<Path> paths = new ArrayList<>();
1555            MemoryFileManager fm = proc.taskFactory.fileManager();
1556
1557            appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, paths);
1558            appendPaths(fm, StandardLocation.CLASS_PATH, paths);
1559            appendPaths(fm, StandardLocation.SOURCE_PATH, paths);
1560
1561            Map<Path, ClassIndex> newIndexes = new HashMap<>();
1562
1563            //setup existing/last known data:
1564            for (Path p : paths) {
1565                ClassIndex index = PATH_TO_INDEX.get(p);
1566                if (index != null) {
1567                    newIndexes.put(p, index);
1568                }
1569            }
1570
1571            synchronized (currentIndexes) {
1572                //temporary setting old data:
1573                currentIndexes.clear();
1574                currentIndexes.putAll(newIndexes);
1575            }
1576
1577            //update/compute the indexes if needed:
1578            for (Path p : paths) {
1579                waitIndexingNotSuspended();
1580
1581                ClassIndex index = indexForPath(p);
1582                newIndexes.put(p, index);
1583            }
1584
1585            synchronized (currentIndexes) {
1586                currentIndexes.clear();
1587                currentIndexes.putAll(newIndexes);
1588            }
1589        } catch (Exception ex) {
1590            proc.debug(ex, "SourceCodeAnalysisImpl.refreshIndexes(" + version + ")");
1591        } finally {
1592            synchronized (currentIndexes) {
1593                indexVersion = version;
1594            }
1595        }
1596    }
1597
1598    private void appendPaths(MemoryFileManager fm, Location loc, Collection<Path> paths) {
1599        Iterable<? extends Path> locationPaths = fm.getLocationAsPaths(loc);
1600        if (locationPaths == null)
1601            return ;
1602        for (Path path : locationPaths) {
1603            if (".".equals(path.toString())) {
1604                //skip CWD
1605                continue;
1606            }
1607
1608            paths.add(path);
1609        }
1610    }
1611
1612    //create/update index a given JavaFileManager entry (which may be a JDK installation, a jar/zip file or a directory):
1613    //if an index exists for the given entry, the existing index is kept unless the timestamp is modified
1614    private ClassIndex indexForPath(Path path) {
1615        if (isJRTMarkerFile(path)) {
1616            FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
1617            Path modules = jrtfs.getPath("modules");
1618            return PATH_TO_INDEX.compute(path, (p, index) -> {
1619                try {
1620                    long lastModified = Files.getLastModifiedTime(modules).toMillis();
1621                    if (index == null || index.timestamp != lastModified) {
1622                        try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) {
1623                            index = doIndex(lastModified, path, stream);
1624                        }
1625                    }
1626                    return index;
1627                } catch (IOException ex) {
1628                    proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")");
1629                    return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap());
1630                }
1631            });
1632        } else if (!Files.isDirectory(path)) {
1633            if (Files.exists(path)) {
1634                return PATH_TO_INDEX.compute(path, (p, index) -> {
1635                    try {
1636                        long lastModified = Files.getLastModifiedTime(p).toMillis();
1637                        if (index == null || index.timestamp != lastModified) {
1638                            ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader();
1639
1640                            try (FileSystem zip = FileSystems.newFileSystem(path, cl)) {
1641                                index = doIndex(lastModified, path, zip.getRootDirectories());
1642                            }
1643                        }
1644                        return index;
1645                    } catch (IOException ex) {
1646                        proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")");
1647                        return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap());
1648                    }
1649                });
1650            } else {
1651                return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap());
1652            }
1653        } else {
1654            return PATH_TO_INDEX.compute(path, (p, index) -> {
1655                //no persistence for directories, as we cannot check timestamps:
1656                if (index == null) {
1657                    index = doIndex(-1, path, Arrays.asList(p));
1658                }
1659                return index;
1660            });
1661        }
1662    }
1663
1664    static boolean isJRTMarkerFile(Path path) {
1665        return path.equals(Paths.get(System.getProperty("java.home"), "lib", "modules"));
1666    }
1667
1668    //create an index based on the content of the given dirs; the original JavaFileManager entry is originalPath.
1669    private ClassIndex doIndex(long timestamp, Path originalPath, Iterable<? extends Path> dirs) {
1670        Set<String> packages = new HashSet<>();
1671        Map<String, Collection<String>> classSimpleName2FQN = new HashMap<>();
1672
1673        for (Path d : dirs) {
1674            try {
1675                Files.walkFileTree(d, new FileVisitor<Path>() {
1676                    int depth;
1677                    @Override
1678                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
1679                        waitIndexingNotSuspended();
1680                        if (depth++ == 0)
1681                            return FileVisitResult.CONTINUE;
1682                        String dirName = dir.getFileName().toString();
1683                        String sep = dir.getFileSystem().getSeparator();
1684                        dirName = dirName.endsWith(sep) ? dirName.substring(0, dirName.length() - sep.length())
1685                                                        : dirName;
1686                        if (SourceVersion.isIdentifier(dirName))
1687                            return FileVisitResult.CONTINUE;
1688                        return FileVisitResult.SKIP_SUBTREE;
1689                    }
1690                    @Override
1691                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
1692                        waitIndexingNotSuspended();
1693                        if (file.getFileName().toString().endsWith(".class")) {
1694                            String relativePath = d.relativize(file).toString();
1695                            String binaryName = relativePath.substring(0, relativePath.length() - 6).replace('/', '.');
1696                            int packageDot = binaryName.lastIndexOf('.');
1697                            if (packageDot > (-1)) {
1698                                packages.add(binaryName.substring(0, packageDot));
1699                            }
1700                            String typeName = binaryName.replace('$', '.');
1701                            addClassName2Map(classSimpleName2FQN, typeName);
1702                        }
1703                        return FileVisitResult.CONTINUE;
1704                    }
1705                    @Override
1706                    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
1707                        return FileVisitResult.CONTINUE;
1708                    }
1709                    @Override
1710                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
1711                        depth--;
1712                        return FileVisitResult.CONTINUE;
1713                    }
1714                });
1715            } catch (IOException ex) {
1716                proc.debug(ex, "doIndex(" + d.toString() + ")");
1717            }
1718        }
1719
1720        return new ClassIndex(timestamp, originalPath, packages, classSimpleName2FQN);
1721    }
1722
1723    private static void addClassName2Map(Map<String, Collection<String>> classSimpleName2FQN, String typeName) {
1724        int simpleNameDot = typeName.lastIndexOf('.');
1725        classSimpleName2FQN.computeIfAbsent(typeName.substring(simpleNameDot + 1), n -> new LinkedHashSet<>())
1726                           .add(typeName);
1727    }
1728
1729    //holder for indexed data about a given path
1730    public static final class ClassIndex {
1731        public final long timestamp;
1732        public final Path forPath;
1733        public final Set<String> packages;
1734        public final Map<String, Collection<String>> classSimpleName2FQN;
1735
1736        public ClassIndex(long timestamp, Path forPath, Set<String> packages, Map<String, Collection<String>> classSimpleName2FQN) {
1737            this.timestamp = timestamp;
1738            this.forPath = forPath;
1739            this.packages = packages;
1740            this.classSimpleName2FQN = classSimpleName2FQN;
1741        }
1742
1743    }
1744
1745    //for tests, to be able to wait until the indexing finishes:
1746    public void waitBackgroundTaskFinished() throws Exception {
1747        boolean upToDate;
1748        synchronized (currentIndexes) {
1749            upToDate = classpathVersion == indexVersion;
1750        }
1751        while (!upToDate) {
1752            INDEXER.submit(() -> {}).get();
1753            synchronized (currentIndexes) {
1754                upToDate = classpathVersion == indexVersion;
1755            }
1756        }
1757    }
1758
1759    /**
1760     * A candidate for continuation of the given user's input.
1761     */
1762    private static class SuggestionImpl implements Suggestion {
1763
1764        private final String continuation;
1765        private final boolean matchesType;
1766
1767        /**
1768         * Create a {@code Suggestion} instance.
1769         *
1770         * @param continuation a candidate continuation of the user's input
1771         * @param matchesType does the candidate match the target type
1772         */
1773        public SuggestionImpl(String continuation, boolean matchesType) {
1774            this.continuation = continuation;
1775            this.matchesType = matchesType;
1776        }
1777
1778        /**
1779         * The candidate continuation of the given user's input.
1780         *
1781         * @return the continuation string
1782         */
1783        @Override
1784        public String continuation() {
1785            return continuation;
1786        }
1787
1788        /**
1789         * Indicates whether input continuation matches the target type and is thus
1790         * more likely to be the desired continuation. A matching continuation is
1791         * preferred.
1792         *
1793         * @return {@code true} if this suggested continuation matches the
1794         * target type; otherwise {@code false}
1795         */
1796        @Override
1797        public boolean matchesType() {
1798            return matchesType;
1799        }
1800    }
1801
1802    /**
1803     * The result of {@code analyzeCompletion(String input)}.
1804     * Describes the completeness and position of the first snippet in the given input.
1805     */
1806    private static class CompletionInfoImpl implements CompletionInfo {
1807
1808        private final Completeness completeness;
1809        private final String source;
1810        private final String remaining;
1811
1812        CompletionInfoImpl(Completeness completeness, String source, String remaining) {
1813            this.completeness = completeness;
1814            this.source = source;
1815            this.remaining = remaining;
1816        }
1817
1818        /**
1819         * The analyzed completeness of the input.
1820         *
1821         * @return an enum describing the completeness of the input string.
1822         */
1823        @Override
1824        public Completeness completeness() {
1825            return completeness;
1826        }
1827
1828        /**
1829         * Input remaining after the complete part of the source.
1830         *
1831         * @return the portion of the input string that remains after the
1832         * complete Snippet
1833         */
1834        @Override
1835        public String remaining() {
1836            return remaining;
1837        }
1838
1839        /**
1840         * Source code for the first Snippet of code input. For example, first
1841         * statement, or first method declaration. Trailing semicolons will be
1842         * added, as needed.
1843         *
1844         * @return the source of the first encountered Snippet
1845         */
1846        @Override
1847        public String source() {
1848            return source;
1849        }
1850    }
1851
1852}
1853