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