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