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