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