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