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