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