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