/** User: catherine */
@State(
    name = "PyPackageService",
    storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/packages.xml")})
public class PyPackageService implements PersistentStateComponent<PyPackageService> {
  public Map<String, Boolean> sdkToUsersite = new HashMap<String, Boolean>();
  public List<String> additionalRepositories = new ArrayList<String>();
  public Map<String, String> PY_PACKAGES = ContainerUtil.newConcurrentMap();
  public String virtualEnvBasePath;
  public Boolean PYPI_REMOVED = false;

  public long LAST_TIME_CHECKED = 0;

  @Override
  public PyPackageService getState() {
    return this;
  }

  @Override
  public void loadState(PyPackageService state) {
    XmlSerializerUtil.copyBean(state, this);
  }

  public void addSdkToUserSite(String sdk, boolean useUsersite) {
    sdkToUsersite.put(sdk, useUsersite);
  }

  public void addRepository(String repository) {
    if (repository == null) return;
    if (repository.startsWith(PyPIPackageUtil.PYPI_HOST)) {
      PYPI_REMOVED = false;
    } else {
      if (!repository.endsWith("/")) repository += "/";
      additionalRepositories.add(repository);
    }
  }

  public void removeRepository(final String repository) {
    if (additionalRepositories.contains(repository)) additionalRepositories.remove(repository);
    else if (repository.startsWith(PyPIPackageUtil.PYPI_HOST)) {
      PYPI_REMOVED = true;
    }
  }

  public boolean useUserSite(String sdk) {
    if (sdkToUsersite.containsKey(sdk)) return sdkToUsersite.get(sdk);
    return false;
  }

  public static PyPackageService getInstance() {
    return ServiceManager.getService(PyPackageService.class);
  }

  public String getVirtualEnvBasePath() {
    return virtualEnvBasePath;
  }

  public void setVirtualEnvBasePath(String virtualEnvBasePath) {
    this.virtualEnvBasePath = virtualEnvBasePath;
  }
}
 @NotNull
 private InspectionTreeNode getToolParentNode(
     @NotNull String groupName, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) {
   if (groupName.isEmpty()) {
     return getRelativeRootNode(groupedBySeverity, errorLevel);
   }
   ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel);
   if (map == null) {
     map =
         ConcurrencyUtil.cacheOrGet(
             myGroups, errorLevel, ContainerUtil.<String, InspectionGroupNode>newConcurrentMap());
   }
   InspectionGroupNode group;
   if (groupedBySeverity) {
     group = map.get(groupName);
   } else {
     group = null;
     for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) {
       if ((group = groupMap.get(groupName)) != null) break;
     }
   }
   if (group == null) {
     group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName));
     addChildNodeInEDT(getRelativeRootNode(groupedBySeverity, errorLevel), group);
   }
   return group;
 }
  public GradleResourceCompilerConfigurationGenerator(@NotNull final Project project) {
    myProject = project;
    myModulesConfigurationHash = ContainerUtil.newConcurrentMap();
    myExternalProjectDataService =
        (ExternalProjectDataService)
            ServiceManager.getService(ProjectDataManager.class)
                .getDataService(ExternalProjectDataService.KEY);
    assert myExternalProjectDataService != null;

    project
        .getMessageBus()
        .connect(project)
        .subscribe(
            ProjectTopics.MODULES,
            new ModuleAdapter() {
              public void moduleRemoved(@NotNull Project project, @NotNull Module module) {
                myModulesConfigurationHash.remove(module.getName());
              }

              @Override
              public void modulesRenamed(
                  @NotNull Project project,
                  @NotNull List<Module> modules,
                  @NotNull Function<Module, String> oldNameProvider) {
                for (Module module : modules) {
                  moduleRemoved(project, module);
                }
              }
            });
  }
 public V getCachedValue(T key) {
   SofterReference<ConcurrentMap<T, V>> ref = myCache;
   ConcurrentMap<T, V> map = ref == null ? null : ref.get();
   if (map == null) {
     myCache = new SofterReference<ConcurrentMap<T, V>>(map = ContainerUtil.newConcurrentMap());
   }
   V value = map.get(key);
   if (value == null) {
     map.put(key, value = myValueProvider.fun(key));
   }
   return value;
 }
  private static void setContinuation(
      CompileContext context, ModuleChunk chunk, @Nullable GroovycContinuation continuation) {
    clearContinuation(context, chunk);
    if (continuation != null) {
      if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
        LOG.info("registering continuation for " + chunk);
      }

      Map<ModuleChunk, GroovycContinuation> map = CONTINUATIONS.get(context);
      if (map == null) CONTINUATIONS.set(context, map = ContainerUtil.newConcurrentMap());
      map.put(chunk, continuation);
    }
  }
  private void updateSelfDependentDtdDescriptors(
      XmlDocumentImpl copy,
      HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict,
      HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict) {
    copy.myDefaultDescriptorsCacheNotStrict = ContainerUtil.newConcurrentMap();
    copy.myDefaultDescriptorsCacheStrict = ContainerUtil.newConcurrentMap();

    for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheStrict.entrySet()) {
      if (e.getValue().hasUpToDateValue()) {
        final XmlNSDescriptor nsDescriptor = e.getValue().getValue();
        if (!isGeneratedFromDtd(nsDescriptor))
          copy.myDefaultDescriptorsCacheStrict.put(e.getKey(), e.getValue());
      }
    }

    for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheNotStrict.entrySet()) {
      if (e.getValue().hasUpToDateValue()) {
        final XmlNSDescriptor nsDescriptor = e.getValue().getValue();
        if (!isGeneratedFromDtd(nsDescriptor))
          copy.myDefaultDescriptorsCacheNotStrict.put(e.getKey(), e.getValue());
      }
    }
  }
  private static class DelegatingDisposable implements Disposable {
    private static final ConcurrentMap<Disposable, DelegatingDisposable> ourInstances =
        ContainerUtil.newConcurrentMap(ContainerUtil.<Disposable>identityStrategy());
    private final TObjectIntHashMap<VirtualFilePointerImpl> myCounts =
        new TObjectIntHashMap<VirtualFilePointerImpl>();
    private final Disposable myParent;

    private DelegatingDisposable(@NotNull Disposable parent) {
      myParent = parent;
    }

    static void registerDisposable(
        @NotNull Disposable parentDisposable, @NotNull VirtualFilePointerImpl pointer) {
      DelegatingDisposable result = ourInstances.get(parentDisposable);
      if (result == null) {
        DelegatingDisposable newDisposable = new DelegatingDisposable(parentDisposable);
        result = ConcurrencyUtil.cacheOrGet(ourInstances, parentDisposable, newDisposable);
        if (result == newDisposable) {
          Disposer.register(parentDisposable, result);
        }
      }

      synchronized (result) {
        result.myCounts.put(pointer, result.myCounts.get(pointer) + 1);
      }
    }

    @Override
    public void dispose() {
      ourInstances.remove(myParent);
      synchronized (this) {
        myCounts.forEachEntry(
            new TObjectIntProcedure<VirtualFilePointerImpl>() {
              @Override
              public boolean execute(VirtualFilePointerImpl pointer, int disposeCount) {
                int after = pointer.myNode.incrementUsageCount(-disposeCount + 1);
                LOG.assertTrue(after > 0, after);
                pointer.dispose();
                return true;
              }
            });
      }
    }
  }
/** @author Mike */
public class XmlDocumentImpl extends XmlElementImpl implements XmlDocument {

  private static final Key<Boolean> AUTO_GENERATED = Key.create("auto-generated xml schema");

  public static boolean isAutoGeneratedSchema(XmlFile file) {
    return file.getUserData(AUTO_GENERATED) != null;
  }

  private static final Logger LOG =
      Logger.getInstance("#com.intellij.psi.impl.source.xml.XmlDocumentImpl");
  private volatile XmlProlog myProlog;
  private volatile XmlTag myRootTag;
  private volatile long myExtResourcesModCount = -1;

  public XmlDocumentImpl() {
    this(XmlElementType.XML_DOCUMENT);
  }

  protected XmlDocumentImpl(IElementType type) {
    super(type);
  }

  @Override
  public void accept(@NotNull PsiElementVisitor visitor) {
    if (visitor instanceof XmlElementVisitor) {
      ((XmlElementVisitor) visitor).visitXmlDocument(this);
    } else {
      visitor.visitElement(this);
    }
  }

  @Override
  public int getChildRole(ASTNode child) {
    LOG.assertTrue(child.getTreeParent() == this);
    IElementType i = child.getElementType();
    if (i == XmlElementType.XML_PROLOG) {
      return XmlChildRole.XML_PROLOG;
    } else if (i == XmlElementType.XML_TAG) {
      return XmlChildRole.XML_TAG;
    } else {
      return ChildRoleBase.NONE;
    }
  }

  @Override
  public XmlProlog getProlog() {
    XmlProlog prolog = myProlog;

    if (prolog == null) {
      synchronized (PsiLock.LOCK) {
        prolog = myProlog;
        if (prolog == null) {
          prolog = (XmlProlog) findElementByTokenType(XmlElementType.XML_PROLOG);
          myProlog = prolog;
        }
      }
    }

    return myProlog;
  }

  @Override
  public XmlTag getRootTag() {
    XmlTag rootTag = myRootTag;

    if (rootTag == null) {
      synchronized (PsiLock.LOCK) {
        rootTag = myRootTag;
        if (rootTag == null) {
          rootTag = (XmlTag) findElementByTokenType(XmlElementType.XML_TAG);
          myRootTag = rootTag;
        }
      }
    }

    return myRootTag;
  }

  @Override
  @SuppressWarnings("ConstantConditions")
  public XmlNSDescriptor getRootTagNSDescriptor() {
    XmlTag rootTag = getRootTag();
    return rootTag != null ? rootTag.getNSDescriptor(rootTag.getNamespace(), false) : null;
  }

  private ConcurrentMap<String, CachedValue<XmlNSDescriptor>> myDefaultDescriptorsCacheStrict =
      ContainerUtil.newConcurrentMap();
  private ConcurrentMap<String, CachedValue<XmlNSDescriptor>> myDefaultDescriptorsCacheNotStrict =
      ContainerUtil.newConcurrentMap();

  @Override
  public void clearCaches() {
    myDefaultDescriptorsCacheStrict.clear();
    myDefaultDescriptorsCacheNotStrict.clear();
    myProlog = null;
    myRootTag = null;
    super.clearCaches();
  }

  @Override
  public XmlNSDescriptor getDefaultNSDescriptor(final String namespace, final boolean strict) {
    long curExtResourcesModCount =
        ExternalResourceManagerEx.getInstanceEx().getModificationCount(getProject());
    if (myExtResourcesModCount != curExtResourcesModCount) {
      myDefaultDescriptorsCacheNotStrict.clear();
      myDefaultDescriptorsCacheStrict.clear();
      myExtResourcesModCount = curExtResourcesModCount;
    }

    final ConcurrentMap<String, CachedValue<XmlNSDescriptor>> defaultDescriptorsCache;
    if (strict) {
      defaultDescriptorsCache = myDefaultDescriptorsCacheStrict;
    } else {
      defaultDescriptorsCache = myDefaultDescriptorsCacheNotStrict;
    }

    CachedValue<XmlNSDescriptor> cachedValue = defaultDescriptorsCache.get(namespace);
    if (cachedValue == null) {
      defaultDescriptorsCache.put(
          namespace,
          cachedValue =
              new PsiCachedValueImpl<XmlNSDescriptor>(
                  getManager(),
                  new CachedValueProvider<XmlNSDescriptor>() {
                    @Override
                    public Result<XmlNSDescriptor> compute() {
                      final XmlNSDescriptor defaultNSDescriptorInner =
                          getDefaultNSDescriptorInner(namespace, strict);

                      if (isGeneratedFromDtd(defaultNSDescriptorInner)) {
                        return new Result<XmlNSDescriptor>(
                            defaultNSDescriptorInner,
                            XmlDocumentImpl.this,
                            ExternalResourceManager.getInstance());
                      }

                      return new Result<XmlNSDescriptor>(
                          defaultNSDescriptorInner,
                          defaultNSDescriptorInner != null
                              ? defaultNSDescriptorInner.getDependences()
                              : ExternalResourceManager.getInstance());
                    }
                  }));
    }
    return cachedValue.getValue();
  }

  private boolean isGeneratedFromDtd(XmlNSDescriptor defaultNSDescriptorInner) {
    if (defaultNSDescriptorInner == null) {
      return false;
    }
    XmlFile descriptorFile = defaultNSDescriptorInner.getDescriptorFile();
    if (descriptorFile == null) {
      return false;
    }
    @NonNls String otherName = XmlUtil.getContainingFile(this).getName() + ".dtd";
    return descriptorFile.getName().equals(otherName);
  }

  private XmlNSDescriptor getDefaultNSDescriptorInner(
      final String namespace, final boolean strict) {
    final XmlFile containingFile = XmlUtil.getContainingFile(this);
    if (containingFile == null) return null;
    final XmlProlog prolog = getProlog();
    final XmlDoctype doctype = prolog != null ? prolog.getDoctype() : null;
    boolean dtdUriFromDocTypeIsNamespace = false;

    if (XmlUtil.HTML_URI.equals(namespace)) {
      XmlNSDescriptor nsDescriptor =
          doctype != null ? getNsDescriptorFormDocType(doctype, containingFile, true) : null;
      if (doctype != null) {
        LOG.debug(
            "Descriptor from doctype "
                + doctype
                + " is "
                + (nsDescriptor != null ? nsDescriptor.getClass().getCanonicalName() : "NULL"));
      }

      if (nsDescriptor == null) {
        String htmlns =
            ExternalResourceManagerEx.getInstanceEx().getDefaultHtmlDoctype(getProject());
        if (htmlns.isEmpty()) {
          htmlns = Html5SchemaProvider.getHtml5SchemaLocation();
        }
        nsDescriptor = getDefaultNSDescriptor(htmlns, false);
      }
      return new HtmlNSDescriptorImpl(nsDescriptor);
    } else if (XmlUtil.XHTML_URI.equals(namespace)) {
      String xhtmlNamespace = XmlUtil.getDefaultXhtmlNamespace(getProject());
      if (xhtmlNamespace == null || xhtmlNamespace.isEmpty()) {
        xhtmlNamespace = Html5SchemaProvider.getXhtml5SchemaLocation();
      }
      return getDefaultNSDescriptor(xhtmlNamespace, false);
    } else if (namespace != null && namespace != XmlUtil.EMPTY_URI) {
      if (doctype == null || !namespace.equals(XmlUtil.getDtdUri(doctype))) {
        boolean documentIsSchemaThatDefinesNs =
            namespace.equals(XmlUtil.getTargetSchemaNsFromTag(getRootTag()));

        final XmlFile xmlFile =
            documentIsSchemaThatDefinesNs
                ? containingFile
                : XmlUtil.findNamespace(containingFile, namespace);
        if (xmlFile != null) {
          final XmlDocument document = xmlFile.getDocument();
          if (document != null) {
            return (XmlNSDescriptor) document.getMetaData();
          }
        }
      } else {
        dtdUriFromDocTypeIsNamespace = true;
      }
    }

    if (strict && !dtdUriFromDocTypeIsNamespace) return null;

    if (doctype != null) {
      XmlNSDescriptor descr = getNsDescriptorFormDocType(doctype, containingFile, false);

      if (descr != null) {
        return XmlExtension.getExtension(containingFile)
            .getDescriptorFromDoctype(containingFile, descr);
      }
    }

    if (strict) return null;
    if (namespace == XmlUtil.EMPTY_URI) {
      final XmlFile xmlFile = XmlUtil.findNamespace(containingFile, namespace);
      if (xmlFile != null) {
        return (XmlNSDescriptor) xmlFile.getDocument().getMetaData();
      }
    }
    try {
      final PsiFile fileFromText =
          PsiFileFactory.getInstance(getProject())
              .createFileFromText(
                  containingFile.getName() + ".dtd",
                  DTDLanguage.INSTANCE,
                  XmlUtil.generateDocumentDTD(this, false),
                  false,
                  false);
      if (fileFromText instanceof XmlFile) {
        fileFromText.putUserData(AUTO_GENERATED, Boolean.TRUE);
        return (XmlNSDescriptor) ((XmlFile) fileFromText).getDocument().getMetaData();
      }
    } catch (ProcessCanceledException ex) {
      throw ex;
    } catch (RuntimeException ignored) {
    } // e.g. dtd isn't mapped to xml type

    return null;
  }

  @NotNull
  private static String getFilePathForLogging(@Nullable PsiFile file) {
    if (file == null) {
      return "NULL";
    }
    final VirtualFile vFile = file.getVirtualFile();
    return vFile != null ? vFile.getPath() : "NULL_VFILE";
  }

  @Nullable
  private XmlNSDescriptor getNsDescriptorFormDocType(
      final XmlDoctype doctype, final XmlFile containingFile, final boolean forHtml) {
    XmlNSDescriptor descriptor = getNSDescriptorFromMetaData(doctype.getMarkupDecl(), true);

    final String filePath = getFilePathForLogging(containingFile);

    final String dtdUri = XmlUtil.getDtdUri(doctype);
    LOG.debug(
        "DTD url for doctype " + doctype.getText() + " in file " + filePath + " is " + dtdUri);

    if (dtdUri != null && !dtdUri.isEmpty()) {
      XmlFile xmlFile = XmlUtil.findNamespace(containingFile, dtdUri);
      if (xmlFile == null) {
        // try to auto-detect it
        xmlFile = XmlNamespaceIndex.guessDtd(dtdUri, containingFile);
      }
      final String schemaFilePath = getFilePathForLogging(xmlFile);

      LOG.debug("Schema file for " + filePath + " is " + schemaFilePath);

      XmlNSDescriptor descriptorFromDtd =
          getNSDescriptorFromMetaData(xmlFile == null ? null : xmlFile.getDocument(), forHtml);

      LOG.debug(
          "Descriptor from meta data for schema file "
              + schemaFilePath
              + " is "
              + (descriptorFromDtd != null
                  ? descriptorFromDtd.getClass().getCanonicalName()
                  : "NULL"));

      if (descriptor != null && descriptorFromDtd != null) {
        descriptor =
            new XmlNSDescriptorSequence(new XmlNSDescriptor[] {descriptor, descriptorFromDtd});
      } else if (descriptorFromDtd != null) {
        descriptor = descriptorFromDtd;
      }
    }
    return descriptor;
  }

  @Nullable
  private XmlNSDescriptor getNSDescriptorFromMetaData(
      @Nullable PsiMetaOwner metaOwner, boolean nonEmpty) {
    if (metaOwner == null) return null;
    XmlNSDescriptor descriptor = (XmlNSDescriptor) metaOwner.getMetaData();
    if (descriptor == null) return null;
    if (nonEmpty && descriptor.getRootElementsDescriptors(this).length == 0) {
      return null;
    }
    return descriptor;
  }

  @Override
  public CompositePsiElement clone() {
    HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict =
        new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheStrict);
    HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict =
        new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheNotStrict);
    final XmlDocumentImpl copy = (XmlDocumentImpl) super.clone();
    updateSelfDependentDtdDescriptors(copy, cacheStrict, cacheNotStrict);
    return copy;
  }

  @Override
  public PsiElement copy() {
    HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict =
        new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheStrict);
    HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict =
        new HashMap<String, CachedValue<XmlNSDescriptor>>(myDefaultDescriptorsCacheNotStrict);
    final XmlDocumentImpl copy = (XmlDocumentImpl) super.copy();
    updateSelfDependentDtdDescriptors(copy, cacheStrict, cacheNotStrict);
    return copy;
  }

  private void updateSelfDependentDtdDescriptors(
      XmlDocumentImpl copy,
      HashMap<String, CachedValue<XmlNSDescriptor>> cacheStrict,
      HashMap<String, CachedValue<XmlNSDescriptor>> cacheNotStrict) {
    copy.myDefaultDescriptorsCacheNotStrict = ContainerUtil.newConcurrentMap();
    copy.myDefaultDescriptorsCacheStrict = ContainerUtil.newConcurrentMap();

    for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheStrict.entrySet()) {
      if (e.getValue().hasUpToDateValue()) {
        final XmlNSDescriptor nsDescriptor = e.getValue().getValue();
        if (!isGeneratedFromDtd(nsDescriptor))
          copy.myDefaultDescriptorsCacheStrict.put(e.getKey(), e.getValue());
      }
    }

    for (Map.Entry<String, CachedValue<XmlNSDescriptor>> e : cacheNotStrict.entrySet()) {
      if (e.getValue().hasUpToDateValue()) {
        final XmlNSDescriptor nsDescriptor = e.getValue().getValue();
        if (!isGeneratedFromDtd(nsDescriptor))
          copy.myDefaultDescriptorsCacheNotStrict.put(e.getKey(), e.getValue());
      }
    }
  }

  @Override
  public PsiMetaData getMetaData() {
    return MetaRegistry.getMeta(this);
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  public void dumpStatistics() {
    System.out.println("Statistics:");
    final TObjectIntHashMap<Object> map = new TObjectIntHashMap<Object>();

    final PsiElementVisitor psiRecursiveElementVisitor =
        new XmlRecursiveElementVisitor() {
          @NonNls private static final String TOKENS_KEY = "Tokens";
          @NonNls private static final String ELEMENTS_KEY = "Elements";

          @Override
          public void visitXmlToken(XmlToken token) {
            inc(TOKENS_KEY);
          }

          @Override
          public void visitElement(PsiElement element) {
            inc(ELEMENTS_KEY);
            super.visitElement(element);
          }

          private void inc(final String key) {
            map.put(key, map.get(key) + 1);
          }
        };

    accept(psiRecursiveElementVisitor);

    final Object[] keys = map.keys();
    for (final Object key : keys) {
      System.out.println(key + ": " + map.get(key));
    }
  }

  @Override
  public TreeElement addInternal(
      final TreeElement first, final ASTNode last, final ASTNode anchor, final Boolean before) {
    final PomModel model = PomManager.getModel(getProject());
    final XmlAspect aspect = model.getModelAspect(XmlAspect.class);
    final TreeElement[] holder = new TreeElement[1];
    try {
      model.runTransaction(
          new PomTransactionBase(this, aspect) {
            @Override
            public PomModelEvent runInner() {
              holder[0] = XmlDocumentImpl.super.addInternal(first, last, anchor, before);
              return XmlDocumentChangedImpl.createXmlDocumentChanged(model, XmlDocumentImpl.this);
            }
          });
    } catch (IncorrectOperationException ignored) {
    }
    return holder[0];
  }

  @Override
  public void deleteChildInternal(@NotNull final ASTNode child) {
    final PomModel model = PomManager.getModel(getProject());
    final XmlAspect aspect = model.getModelAspect(XmlAspect.class);
    try {
      model.runTransaction(
          new PomTransactionBase(this, aspect) {
            @Override
            public PomModelEvent runInner() {
              XmlDocumentImpl.super.deleteChildInternal(child);
              return XmlDocumentChangedImpl.createXmlDocumentChanged(model, XmlDocumentImpl.this);
            }
          });
    } catch (IncorrectOperationException ignored) {
    }
  }

  @Override
  public void replaceChildInternal(
      @NotNull final ASTNode child, @NotNull final TreeElement newElement) {
    final PomModel model = PomManager.getModel(getProject());
    final XmlAspect aspect = model.getModelAspect(XmlAspect.class);
    try {
      model.runTransaction(
          new PomTransactionBase(this, aspect) {
            @Override
            public PomModelEvent runInner() {
              XmlDocumentImpl.super.replaceChildInternal(child, newElement);
              return XmlDocumentChangedImpl.createXmlDocumentChanged(model, XmlDocumentImpl.this);
            }
          });
    } catch (IncorrectOperationException ignored) {
    }
  }
}
Example #9
0
public class IndentsPass extends TextEditorHighlightingPass implements DumbAware {
  private static final ConcurrentMap<IElementType, String> COMMENT_PREFIXES =
      ContainerUtil.newConcurrentMap();
  private static final String NO_COMMENT_INFO_MARKER =
      "hopefully, noone uses this string as a comment prefix";

  private static final Key<List<RangeHighlighter>> INDENT_HIGHLIGHTERS_IN_EDITOR_KEY =
      Key.create("INDENT_HIGHLIGHTERS_IN_EDITOR_KEY");
  private static final Key<Long> LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT");

  private final EditorEx myEditor;
  private final PsiFile myFile;
  public static final Comparator<TextRange> RANGE_COMPARATOR =
      new Comparator<TextRange>() {
        @Override
        public int compare(TextRange o1, TextRange o2) {
          if (o1.getStartOffset() == o2.getStartOffset()) {
            return o1.getEndOffset() - o2.getEndOffset();
          }

          return o1.getStartOffset() - o2.getStartOffset();
        }
      };

  private static final CustomHighlighterRenderer RENDERER =
      new CustomHighlighterRenderer() {
        @Override
        @SuppressWarnings({"AssignmentToForLoopParameter"})
        public void paint(
            @NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) {
          int startOffset = highlighter.getStartOffset();
          final Document doc = highlighter.getDocument();
          if (startOffset >= doc.getTextLength()) return;

          final int endOffset = highlighter.getEndOffset();
          final int endLine = doc.getLineNumber(endOffset);

          int off;
          int startLine = doc.getLineNumber(startOffset);
          IndentGuideDescriptor descriptor =
              editor.getIndentsModel().getDescriptor(startLine, endLine);

          final CharSequence chars = doc.getCharsSequence();
          do {
            int start = doc.getLineStartOffset(startLine);
            int end = doc.getLineEndOffset(startLine);
            off = CharArrayUtil.shiftForward(chars, start, end, " \t");
            startLine--;
          } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n');

          final VisualPosition startPosition = editor.offsetToVisualPosition(off);
          int indentColumn = startPosition.column;

          // It's considered that indent guide can cross not only white space but comments, javadocs
          // etc. Hence, there is a possible
          // case that the first indent guide line is, say, single-line comment where comment
          // symbols ('//') are located at the first
          // visual column. We need to calculate correct indent guide column then.
          int lineShift = 1;
          if (indentColumn <= 0 && descriptor != null) {
            indentColumn = descriptor.indentLevel;
            lineShift = 0;
          }
          if (indentColumn <= 0) return;

          final FoldingModel foldingModel = editor.getFoldingModel();
          if (foldingModel.isOffsetCollapsed(off)) return;

          final FoldRegion headerRegion =
              foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off)));
          final FoldRegion tailRegion =
              foldingModel.getCollapsedRegionAtOffset(
                  doc.getLineStartOffset(doc.getLineNumber(endOffset)));

          if (tailRegion != null && tailRegion == headerRegion) return;

          final boolean selected;
          final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide();
          if (guide != null) {
            final CaretModel caretModel = editor.getCaretModel();
            final int caretOffset = caretModel.getOffset();
            selected =
                caretOffset >= off
                    && caretOffset < endOffset
                    && caretModel.getLogicalPosition().column == indentColumn;
          } else {
            selected = false;
          }

          Point start =
              editor.visualPositionToXY(
                  new VisualPosition(startPosition.line + lineShift, indentColumn));
          final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset);
          Point end =
              editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column));
          int maxY = end.y;
          if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) {
            maxY += editor.getLineHeight();
          }

          Rectangle clip = g.getClipBounds();
          if (clip != null) {
            if (clip.y >= maxY || clip.y + clip.height <= start.y) {
              return;
            }
            maxY = Math.min(maxY, clip.y + clip.height);
          }

          final EditorColorsScheme scheme = editor.getColorsScheme();
          g.setColor(
              selected
                  ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR)
                  : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR));

          // There is a possible case that indent line intersects soft wrap-introduced text.
          // Example:
          //     this is a long line <soft-wrap>
          // that| is soft-wrapped
          //     |
          //     | <- vertical indent
          //
          // Also it's possible that no additional intersections are added because of soft wrap:
          //     this is a long line <soft-wrap>
          //     |   that is soft-wrapped
          //     |
          //     | <- vertical indent
          // We want to use the following approach then:
          //     1. Show only active indent if it crosses soft wrap-introduced text;
          //     2. Show indent as is if it doesn't intersect with soft wrap-introduced text;
          if (selected) {
            g.drawLine(start.x + 2, start.y, start.x + 2, maxY);
          } else {
            int y = start.y;
            int newY = start.y;
            SoftWrapModel softWrapModel = editor.getSoftWrapModel();
            int lineHeight = editor.getLineHeight();
            for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) {
              List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i);
              int logicalLineHeight = softWraps.size() * lineHeight;
              if (i > startLine + lineShift) {
                logicalLineHeight +=
                    lineHeight; // We assume that initial 'y' value points just below the target
                // line.
              }
              if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) {
                if (y < newY
                    || i
                        > startLine
                            + lineShift) { // There is a possible case that soft wrap is located on
                  // indent start line.
                  g.drawLine(start.x + 2, y, start.x + 2, newY + lineHeight);
                }
                newY += logicalLineHeight;
                y = newY;
              } else {
                newY += logicalLineHeight;
              }

              FoldRegion foldRegion =
                  foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i));
              if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) {
                i = doc.getLineNumber(foldRegion.getEndOffset());
              }
            }

            if (y < maxY) {
              g.drawLine(start.x + 2, y, start.x + 2, maxY);
            }
          }
        }
      };
  private volatile List<TextRange> myRanges;
  private volatile List<IndentGuideDescriptor> myDescriptors;

  public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
    super(project, editor.getDocument(), false);
    myEditor = (EditorEx) editor;
    myFile = file;
  }

  @Override
  public void doCollectInformation(@NotNull ProgressIndicator progress) {
    assert myDocument != null;
    final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT);
    if (stamp != null && stamp.longValue() == nowStamp()) return;

    myDescriptors = buildDescriptors();

    ArrayList<TextRange> ranges = new ArrayList<TextRange>();
    for (IndentGuideDescriptor descriptor : myDescriptors) {
      ProgressManager.checkCanceled();
      int endOffset =
          descriptor.endLine < myDocument.getLineCount()
              ? myDocument.getLineStartOffset(descriptor.endLine)
              : myDocument.getTextLength();
      ranges.add(new TextRange(myDocument.getLineStartOffset(descriptor.startLine), endOffset));
    }

    Collections.sort(ranges, RANGE_COMPARATOR);
    myRanges = ranges;
  }

  private long nowStamp() {
    if (!myEditor.getSettings().isIndentGuidesShown()) return -1;
    assert myDocument != null;
    return myDocument.getModificationStamp();
  }

  @Override
  public void doApplyInformationToEditor() {
    final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT);
    if (stamp != null && stamp.longValue() == nowStamp()) return;

    List<RangeHighlighter> oldHighlighters =
        myEditor.getUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY);
    final List<RangeHighlighter> newHighlighters = new ArrayList<RangeHighlighter>();
    final MarkupModel mm = myEditor.getMarkupModel();

    int curRange = 0;

    if (oldHighlighters != null) {
      int curHighlight = 0;
      while (curRange < myRanges.size() && curHighlight < oldHighlighters.size()) {
        TextRange range = myRanges.get(curRange);
        RangeHighlighter highlighter = oldHighlighters.get(curHighlight);

        int cmp = compare(range, highlighter);
        if (cmp < 0) {
          newHighlighters.add(createHighlighter(mm, range));
          curRange++;
        } else if (cmp > 0) {
          highlighter.dispose();
          curHighlight++;
        } else {
          newHighlighters.add(highlighter);
          curHighlight++;
          curRange++;
        }
      }

      for (; curHighlight < oldHighlighters.size(); curHighlight++) {
        RangeHighlighter highlighter = oldHighlighters.get(curHighlight);
        highlighter.dispose();
      }
    }

    final int startRangeIndex = curRange;
    assert myDocument != null;
    DocumentUtil.executeInBulk(
        myDocument,
        myRanges.size() > 10000,
        new Runnable() {
          @Override
          public void run() {
            for (int i = startRangeIndex; i < myRanges.size(); i++) {
              newHighlighters.add(createHighlighter(mm, myRanges.get(i)));
            }
          }
        });

    myEditor.putUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY, newHighlighters);
    myEditor.putUserData(LAST_TIME_INDENTS_BUILT, nowStamp());
    myEditor.getIndentsModel().assumeIndents(myDescriptors);
  }

  private List<IndentGuideDescriptor> buildDescriptors() {
    if (!myEditor.getSettings().isIndentGuidesShown()) return Collections.emptyList();

    IndentsCalculator calculator = new IndentsCalculator();
    calculator.calculate();
    int[] lineIndents = calculator.lineIndents;
    TIntIntHashMap effectiveCommentColumns = calculator.indentAfterUncomment;

    List<IndentGuideDescriptor> descriptors = new ArrayList<IndentGuideDescriptor>();

    IntStack lines = new IntStack();
    IntStack indents = new IntStack();

    lines.push(0);
    indents.push(0);
    assert myDocument != null;
    final CharSequence chars = myDocument.getCharsSequence();
    for (int line = 1; line < lineIndents.length; line++) {
      ProgressManager.checkCanceled();
      int curIndent = lineIndents[line];

      while (!indents.empty() && curIndent <= indents.peek()) {
        ProgressManager.checkCanceled();
        final int level = indents.pop();
        int startLine = lines.pop();
        if (level > 0) {
          boolean addDescriptor =
              effectiveCommentColumns.contains(startLine); // Indent started at comment
          if (!addDescriptor) {
            for (int i = startLine; i < line; i++) {
              if (level != lineIndents[i] && level != effectiveCommentColumns.get(i)) {
                addDescriptor = true;
                break;
              }
            }
          }
          if (addDescriptor) {
            descriptors.add(createDescriptor(level, startLine, line, chars));
          }
        }
      }

      int prevLine = line - 1;
      int prevIndent = lineIndents[prevLine];

      if (curIndent - prevIndent > 1) {
        lines.push(prevLine);
        indents.push(prevIndent);
      }
    }

    while (!indents.empty()) {
      ProgressManager.checkCanceled();
      final int level = indents.pop();
      int startLine = lines.pop();
      if (level > 0) {
        descriptors.add(createDescriptor(level, startLine, myDocument.getLineCount(), chars));
      }
    }
    return descriptors;
  }

  private IndentGuideDescriptor createDescriptor(
      int level, int startLine, int endLine, CharSequence chars) {
    while (startLine > 0 && isBlankLine(startLine, chars)) startLine--;
    return new IndentGuideDescriptor(level, startLine, endLine);
  }

  private boolean isBlankLine(int line, CharSequence chars) {
    Document document = myDocument;
    if (document == null) {
      return true;
    }
    int startOffset = document.getLineStartOffset(line);
    int endOffset = document.getLineEndOffset(line);
    return CharArrayUtil.shiftForward(chars, startOffset, endOffset, " \t")
        >= myDocument.getLineEndOffset(line);
  }

  /**
   * We want to treat comments specially in a way to skip comment prefix on line indent calculation.
   *
   * <p>Example:
   *
   * <pre>
   *   if (true) {
   *     int i1;
   * //    int i2;
   *     int i3;
   *   }
   * </pre>
   *
   * We want to use 'int i2;' start offset as the third line indent (though it has non-white space
   * comment prefix (//) at the first column.
   *
   * <p>This method tries to parse comment prefix for the language implied by the given comment
   * type. It uses {@link #NO_COMMENT_INFO_MARKER} as an indicator that that information is
   * unavailable
   *
   * @param commentType target comment type
   * @return prefix of the comment denoted by the given type if any; {@link #NO_COMMENT_INFO_MARKER}
   *     otherwise
   */
  @NotNull
  private static String getCommentPrefix(@NotNull IElementType commentType) {
    Commenter c = LanguageCommenters.INSTANCE.forLanguage(commentType.getLanguage());
    if (!(c instanceof CodeDocumentationAwareCommenter)) {
      COMMENT_PREFIXES.put(commentType, NO_COMMENT_INFO_MARKER);
      return NO_COMMENT_INFO_MARKER;
    }
    CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter) c;

    IElementType lineCommentType = commenter.getLineCommentTokenType();
    String lineCommentPrefix = commenter.getLineCommentPrefix();
    if (lineCommentType != null) {
      COMMENT_PREFIXES.put(
          lineCommentType, lineCommentPrefix == null ? NO_COMMENT_INFO_MARKER : lineCommentPrefix);
    }

    IElementType blockCommentType = commenter.getBlockCommentTokenType();
    String blockCommentPrefix = commenter.getBlockCommentPrefix();
    if (blockCommentType != null) {
      COMMENT_PREFIXES.put(
          blockCommentType,
          blockCommentPrefix == null ? NO_COMMENT_INFO_MARKER : blockCommentPrefix);
    }

    IElementType docCommentType = commenter.getDocumentationCommentTokenType();
    String docCommentPrefix = commenter.getDocumentationCommentPrefix();
    if (docCommentType != null) {
      COMMENT_PREFIXES.put(
          docCommentType, docCommentPrefix == null ? NO_COMMENT_INFO_MARKER : docCommentPrefix);
    }

    COMMENT_PREFIXES.putIfAbsent(commentType, NO_COMMENT_INFO_MARKER);
    return COMMENT_PREFIXES.get(commentType);
  }

  @NotNull
  private static RangeHighlighter createHighlighter(MarkupModel mm, TextRange range) {
    final RangeHighlighter highlighter =
        mm.addRangeHighlighter(
            range.getStartOffset(),
            range.getEndOffset(),
            0,
            null,
            HighlighterTargetArea.EXACT_RANGE);
    highlighter.setCustomRenderer(RENDERER);
    return highlighter;
  }

  private static int compare(@NotNull TextRange r, @NotNull RangeHighlighter h) {
    int answer = r.getStartOffset() - h.getStartOffset();
    return answer != 0 ? answer : r.getEndOffset() - h.getEndOffset();
  }

  private class IndentsCalculator {

    @NotNull public final Map<Language, TokenSet> myComments = ContainerUtilRt.newHashMap();

    /**
     * We need to treat specially commented lines. Consider a situation like below:
     *
     * <pre>
     *   void test() {
     *     if (true) {
     *       int i;
     *  //     int j;
     *     }
     *   }
     * </pre>
     *
     * We don't want to show indent guide after 'int i;' line because un-commented line below ('int
     * j;') would have the same indent level. That's why we remember 'indents after un-comment' at
     * this collection.
     */
    @NotNull
    public final TIntIntHashMap /* line -> indent column after un-comment */ indentAfterUncomment =
        new TIntIntHashMap();

    @NotNull public final int[] lineIndents;
    @NotNull public final CharSequence myChars;

    IndentsCalculator() {
      assert myDocument != null;
      lineIndents = new int[myDocument.getLineCount()];
      myChars = myDocument.getCharsSequence();
    }

    /** Calculates line indents for the {@link #myDocument target document}. */
    void calculate() {
      final FileType fileType = myFile.getFileType();
      int prevLineIndent = -1;

      for (int line = 0; line < lineIndents.length; line++) {
        ProgressManager.checkCanceled();
        int lineStart = myDocument.getLineStartOffset(line);
        int lineEnd = myDocument.getLineEndOffset(line);
        final int nonWhitespaceOffset =
            CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t");
        if (nonWhitespaceOffset == lineEnd) {
          lineIndents[line] = -1; // Blank line marker
        } else {
          final int column =
              ((EditorImpl) myEditor).calcColumnNumber(nonWhitespaceOffset, line, true, myChars);
          if (prevLineIndent > 0 && prevLineIndent > column) {
            lineIndents[line] = calcIndent(line, nonWhitespaceOffset, lineEnd, column);
          } else {
            lineIndents[line] = column;
          }
          prevLineIndent = lineIndents[line];
        }
      }

      int topIndent = 0;
      for (int line = 0; line < lineIndents.length; line++) {
        ProgressManager.checkCanceled();
        if (lineIndents[line] >= 0) {
          topIndent = lineIndents[line];
        } else {
          int startLine = line;
          while (line < lineIndents.length && lineIndents[line] < 0) {
            //noinspection AssignmentToForLoopParameter
            line++;
          }

          int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent;

          int indent = Math.min(topIndent, bottomIndent);
          if (bottomIndent < topIndent) {
            int lineStart = myDocument.getLineStartOffset(line);
            int lineEnd = myDocument.getLineEndOffset(line);
            int nonWhitespaceOffset =
                CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t");
            HighlighterIterator iterator =
                myEditor.getHighlighter().createIterator(nonWhitespaceOffset);
            if (BraceMatchingUtil.isRBraceToken(iterator, myChars, fileType)) {
              indent = topIndent;
            }
          }

          for (int blankLine = startLine; blankLine < line; blankLine++) {
            assert lineIndents[blankLine] == -1;
            lineIndents[blankLine] = Math.min(topIndent, indent);
          }

          //noinspection AssignmentToForLoopParameter
          line--; // will be incremented back at the end of the loop;
        }
      }
    }

    /**
     * Tries to calculate given line's indent column assuming that there might be a comment at the
     * given indent offset (see {@link #getCommentPrefix(IElementType)}).
     *
     * @param line target line
     * @param indentOffset start indent offset to use for the given line
     * @param lineEndOffset given line's end offset
     * @param fallbackColumn column to return if it's not possible to apply comment-specific indent
     *     calculation rules
     * @return given line's indent column to use
     */
    private int calcIndent(int line, int indentOffset, int lineEndOffset, int fallbackColumn) {
      final HighlighterIterator it = myEditor.getHighlighter().createIterator(indentOffset);
      IElementType tokenType = it.getTokenType();
      Language language = tokenType.getLanguage();
      TokenSet comments = myComments.get(language);
      if (comments == null) {
        ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
        if (definition != null) {
          comments = definition.getCommentTokens();
        }
        if (comments == null) {
          return fallbackColumn;
        } else {
          myComments.put(language, comments);
        }
      }
      if (comments.contains(tokenType) && indentOffset == it.getStart()) {
        String prefix = COMMENT_PREFIXES.get(tokenType);
        if (prefix == null) {
          prefix = getCommentPrefix(tokenType);
        }
        if (!NO_COMMENT_INFO_MARKER.equals(prefix)) {
          final int indentInsideCommentOffset =
              CharArrayUtil.shiftForward(
                  myChars, indentOffset + prefix.length(), lineEndOffset, " \t");
          if (indentInsideCommentOffset < lineEndOffset) {
            int indent = myEditor.calcColumnNumber(indentInsideCommentOffset, line);
            indentAfterUncomment.put(line, indent - prefix.length());
            return indent;
          }
        }
      }
      return fallbackColumn;
    }
  }
}
public class DumbServiceImpl extends DumbService implements Disposable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl");
  private volatile boolean myDumb = false;
  private final DumbModeListener myPublisher;
  private final Queue<DumbModeTask> myUpdatesQueue = new Queue<DumbModeTask>(5);

  /**
   * Per-task progress indicators. Modified from EDT only. The task is removed from this map after
   * it's finished or when the project is disposed.
   */
  private final Map<DumbModeTask, ProgressIndicatorEx> myProgresses =
      ContainerUtil.newConcurrentMap();

  private final Queue<Runnable> myRunWhenSmartQueue = new Queue<Runnable>(5);
  private final Project myProject;

  public DumbServiceImpl(Project project) {
    myProject = project;
    myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE);
  }

  @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"})
  public static DumbServiceImpl getInstance(@NotNull Project project) {
    return (DumbServiceImpl) DumbService.getInstance(project);
  }

  @Override
  public void queueTask(@NotNull final DumbModeTask task) {
    scheduleCacheUpdate(task, true);
  }

  @Override
  public void cancelTask(@NotNull DumbModeTask task) {
    if (ApplicationManager.getApplication().isInternal()) LOG.info("cancel " + task);
    ProgressIndicatorEx indicator = myProgresses.get(task);
    if (indicator != null) {
      indicator.cancel();
    }
  }

  @Override
  public void dispose() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    myUpdatesQueue.clear();
    myRunWhenSmartQueue.clear();
    for (DumbModeTask task : new ArrayList<DumbModeTask>(myProgresses.keySet())) {
      cancelTask(task);
      Disposer.dispose(task);
    }
  }

  @Override
  public Project getProject() {
    return myProject;
  }

  @Override
  public boolean isDumb() {
    return myDumb;
  }

  @TestOnly
  public void setDumb(boolean dumb) {
    if (dumb) {
      myDumb = true;
      myPublisher.enteredDumbMode();
    } else {
      updateFinished();
    }
  }

  @Override
  public void runWhenSmart(@NotNull Runnable runnable) {
    if (!isDumb()) {
      runnable.run();
    } else {
      synchronized (myRunWhenSmartQueue) {
        myRunWhenSmartQueue.addLast(runnable);
      }
    }
  }

  @SuppressWarnings("deprecation")
  public void queueCacheUpdate(@NotNull Collection<CacheUpdater> updaters) {
    scheduleCacheUpdate(
        new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters)), false);
  }

  @SuppressWarnings("deprecation")
  public void queueCacheUpdateInDumbMode(@NotNull Collection<CacheUpdater> updaters) {
    scheduleCacheUpdate(
        new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters)), true);
  }

  private void scheduleCacheUpdate(@NotNull final DumbModeTask task, boolean forceDumbMode) {
    if (ApplicationManager.getApplication().isInternal()) LOG.info("schedule " + task);
    final Application application = ApplicationManager.getApplication();

    if (application.isUnitTestMode()
        || application.isHeadlessEnvironment()
        || !forceDumbMode && !myDumb && application.isReadAccessAllowed()) {
      final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
      if (indicator != null) {
        indicator.pushState();
      }
      try {
        HeavyProcessLatch.INSTANCE.processStarted();
        task.performInDumbMode(indicator != null ? indicator : new EmptyProgressIndicator());
      } finally {
        HeavyProcessLatch.INSTANCE.processFinished();
        if (indicator != null) {
          indicator.popState();
        }
        Disposer.dispose(task);
      }
      return;
    }

    UIUtil.invokeLaterIfNeeded(
        new DumbAwareRunnable() {
          @Override
          public void run() {
            if (myProject.isDisposed()) {
              return;
            }
            final ProgressIndicatorBase indicator = new ProgressIndicatorBase();
            myProgresses.put(task, indicator);
            Disposer.register(
                task,
                new Disposable() {
                  @Override
                  public void dispose() {
                    application.assertIsDispatchThread();
                    myProgresses.remove(task);
                  }
                });
            // ok to test and set the flag like this, because the change is always done from
            // dispatch thread
            if (!myDumb) {
              // always change dumb status inside write action.
              // This will ensure all active read actions are completed before the app goes dumb
              boolean startSuccess =
                  application.runWriteAction(
                      new Computable<Boolean>() {
                        @Override
                        public Boolean compute() {
                          myDumb = true;
                          try {
                            myPublisher.enteredDumbMode();
                          } catch (Throwable e) {
                            LOG.error(e);
                          }

                          try {
                            startBackgroundProcess(task, indicator);
                          } catch (Throwable e) {
                            LOG.error("Failed to start background index update task", e);
                            return false;
                          }
                          return true;
                        }
                      });
              if (!startSuccess) {
                updateFinished();
              }
            } else {
              myUpdatesQueue.addLast(task);
            }
          }
        });
  }

  private void updateFinished() {
    myDumb = false;
    if (myProject.isDisposed()) return;

    if (ApplicationManager.getApplication().isInternal()) LOG.info("updateFinished");

    try {
      myPublisher.exitDumbMode();
      FileEditorManagerEx.getInstanceEx(myProject).refreshIcons();
    } finally {
      // It may happen that one of the pending runWhenSmart actions triggers new dumb mode;
      // in this case we should quit processing pending actions and postpone them until the newly
      // started dumb mode finishes.
      while (!myDumb) {
        final Runnable runnable;
        synchronized (myRunWhenSmartQueue) {
          if (myRunWhenSmartQueue.isEmpty()) {
            break;
          }
          runnable = myRunWhenSmartQueue.pullFirst();
        }
        try {
          runnable.run();
        } catch (Throwable e) {
          LOG.error(e);
        }
      }
    }
  }

  @Override
  public void showDumbModeNotification(@NotNull final String message) {
    UIUtil.invokeLaterIfNeeded(
        new Runnable() {
          @Override
          public void run() {
            final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject);
            if (ideFrame != null) {
              StatusBarEx statusBar = (StatusBarEx) ideFrame.getStatusBar();
              statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, null);
            }
          }
        });
  }

  @Override
  public void waitForSmartMode() {
    final Application application = ApplicationManager.getApplication();
    if (!application.isUnitTestMode()) {
      assert !application.isDispatchThread();
      assert !application.isReadAccessAllowed();
    }

    if (!isDumb()) {
      return;
    }

    final Semaphore semaphore = new Semaphore();
    semaphore.down();
    runWhenSmart(
        new Runnable() {
          @Override
          public void run() {
            semaphore.up();
          }
        });
    semaphore.waitFor();
  }

  @Override
  public JComponent wrapGently(
      @NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) {
    final DumbUnawareHider wrapper = new DumbUnawareHider(dumbUnawareContent);
    wrapper.setContentVisible(!isDumb());
    getProject()
        .getMessageBus()
        .connect(parentDisposable)
        .subscribe(
            DUMB_MODE,
            new DumbModeListener() {

              @Override
              public void enteredDumbMode() {
                wrapper.setContentVisible(false);
              }

              @Override
              public void exitDumbMode() {
                wrapper.setContentVisible(true);
              }
            });

    return wrapper;
  }

  public void smartInvokeLater(@NotNull final Runnable runnable) {
    ApplicationManager.getApplication()
        .invokeLater(
            new Runnable() {
              @Override
              public void run() {
                runWhenSmart(runnable);
              }
            },
            myProject.getDisposed());
  }

  public void smartInvokeLater(
      @NotNull final Runnable runnable, @NotNull ModalityState modalityState) {
    ApplicationManager.getApplication()
        .invokeLater(
            new Runnable() {
              @Override
              public void run() {
                runWhenSmart(runnable);
              }
            },
            modalityState,
            myProject.getDisposed());
  }

  private void startBackgroundProcess(
      @NotNull final DumbModeTask task, @NotNull final ProgressIndicatorEx indicator) {
    ProgressManager.getInstance()
        .run(
            new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) {

              @Override
              public void run(@NotNull final ProgressIndicator visibleIndicator) {
                if (ApplicationManager.getApplication().isInternal())
                  LOG.info("Running dumb mode task: " + task);

                final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance();
                final Thread self = Thread.currentThread();
                try {
                  HeavyProcessLatch.INSTANCE.processStarted();
                  shutdownTracker.registerStopperThread(self);

                  indicator.checkCanceled();

                  if (visibleIndicator instanceof ProgressIndicatorEx) {
                    indicator.addStateDelegate((ProgressIndicatorEx) visibleIndicator);
                    ((ProgressIndicatorEx) visibleIndicator)
                        .addStateDelegate(new AppIconProgress());
                  }

                  indicator.setIndeterminate(true);
                  indicator.setText(IdeBundle.message("progress.indexing.scanning"));

                  task.performInDumbMode(indicator);
                } catch (ProcessCanceledException ignored) {
                } catch (Throwable unexpected) {
                  LOG.error(unexpected);
                } finally {
                  shutdownTracker.unregisterStopperThread(self);
                  HeavyProcessLatch.INSTANCE.processFinished();
                  taskFinished(task);
                }
              }
            });
  }

  private void taskFinished(@NotNull final DumbModeTask prevTask) {
    UIUtil.invokeAndWaitIfNeeded(
        new Runnable() {
          @Override
          public void run() {
            if (myProject.isDisposed()) return;
            Disposer.dispose(prevTask);

            while (true) {
              if (myUpdatesQueue.isEmpty()) {
                updateFinished();
                return;
              }

              DumbModeTask queuedTask = myUpdatesQueue.pullFirst();
              ProgressIndicatorEx indicator = myProgresses.get(queuedTask);
              if (indicator.isCanceled()) {
                Disposer.dispose(queuedTask);
                continue;
              }

              startBackgroundProcess(queuedTask, indicator);
              return;
            }
          }
        });
  }

  private class AppIconProgress extends ProgressIndicatorBase {
    private double lastFraction;

    @Override
    public void setFraction(final double fraction) {
      if (fraction - lastFraction < 0.01d) return;
      lastFraction = fraction;
      UIUtil.invokeLaterIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              AppIcon.getInstance()
                  .setProgress(
                      myProject, "indexUpdate", AppIconScheme.Progress.INDEXING, fraction, true);
            }
          });
    }

    @Override
    public void finish(@NotNull TaskInfo task) {
      if (lastFraction != 0) { // we should call setProgress at least once before
        UIUtil.invokeLaterIfNeeded(
            new Runnable() {
              @Override
              public void run() {
                AppIcon appIcon = AppIcon.getInstance();
                if (appIcon.hideProgress(myProject, "indexUpdate")) {
                  appIcon.requestAttention(myProject, false);
                  appIcon.setOkBadge(myProject, true);
                }
              }
            });
      }
    }
  }
}
public class PsiElementFactoryImpl extends PsiJavaParserFacadeImpl implements PsiElementFactory {
  private final NotNullLazyValue<PsiClass> myArrayClass =
      new AtomicNotNullLazyValue<PsiClass>() {
        @NotNull
        @Override
        protected PsiClass compute() {
          return createArrayClass(
              "public class __Array__{\n public final int length;\n public Object clone() {}\n}",
              LanguageLevel.JDK_1_3);
        }
      };

  private final NotNullLazyValue<PsiClass> myArrayClass15 =
      new AtomicNotNullLazyValue<PsiClass>() {
        @NotNull
        @Override
        protected PsiClass compute() {
          return createArrayClass(
              "public class __Array__<T> {\n public final int length;\n public T[] clone() {}\n}",
              LanguageLevel.JDK_1_5);
        }
      };

  private final ConcurrentMap<GlobalSearchScope, PsiClassType> myCachedObjectType =
      ContainerUtil.newConcurrentMap();

  public PsiElementFactoryImpl(final PsiManagerEx manager) {
    super(manager);
    manager.registerRunnableToRunOnChange(
        new Runnable() {
          @Override
          public void run() {
            myCachedObjectType.clear();
          }
        });
  }

  @NotNull
  @Override
  public PsiClass getArrayClass(@NotNull LanguageLevel languageLevel) {
    return (languageLevel.isAtLeast(LanguageLevel.JDK_1_5) ? myArrayClass15 : myArrayClass)
        .getValue();
  }

  private PsiClass createArrayClass(String text, LanguageLevel level) {
    PsiClass psiClass =
        ((PsiExtensibleClass) createClassFromText(text, null)).getOwnInnerClasses().get(0);
    ensureNonWritable(psiClass);
    PsiFile file = psiClass.getContainingFile();
    ((PsiJavaFileBaseImpl) file).clearCaches();
    PsiUtil.FILE_LANGUAGE_LEVEL_KEY.set(file, level);
    return psiClass;
  }

  private static void ensureNonWritable(PsiClass arrayClass) {
    try {
      arrayClass.getContainingFile().getViewProvider().getVirtualFile().setWritable(false);
    } catch (IOException ignored) {
    }
  }

  @NotNull
  @Override
  public PsiClassType getArrayClassType(
      @NotNull final PsiType componentType, @NotNull final LanguageLevel languageLevel) {
    final PsiClass arrayClass = getArrayClass(languageLevel);
    final PsiTypeParameter[] typeParameters = arrayClass.getTypeParameters();

    PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
    if (typeParameters.length == 1) {
      substitutor = substitutor.put(typeParameters[0], componentType);
    }

    return createType(arrayClass, substitutor);
  }

  @NotNull
  @Override
  public PsiClassType createType(@NotNull PsiClass resolve, @NotNull PsiSubstitutor substitutor) {
    return new PsiImmediateClassType(resolve, substitutor);
  }

  @NotNull
  @Override
  public PsiClassType createType(
      @NotNull PsiClass resolve,
      @NotNull PsiSubstitutor substitutor,
      @Nullable LanguageLevel languageLevel) {
    return new PsiImmediateClassType(resolve, substitutor, languageLevel);
  }

  @NotNull
  @Override
  public PsiClassType createType(
      @NotNull PsiClass resolve,
      @NotNull PsiSubstitutor substitutor,
      @Nullable LanguageLevel languageLevel,
      @NotNull PsiAnnotation[] annotations) {
    return new PsiImmediateClassType(resolve, substitutor, languageLevel, annotations);
  }

  @NotNull
  @Override
  public PsiClass createClass(@NotNull final String name) throws IncorrectOperationException {
    return createClassInner("class", name);
  }

  @NotNull
  @Override
  public PsiClass createInterface(@NotNull final String name) throws IncorrectOperationException {
    return createClassInner("interface", name);
  }

  @NotNull
  @Override
  public PsiClass createEnum(@NotNull final String name) throws IncorrectOperationException {
    return createClassInner("enum", name);
  }

  @NotNull
  @Override
  public PsiClass createAnnotationType(@NotNull @NonNls String name)
      throws IncorrectOperationException {
    return createClassInner("@interface", name);
  }

  private PsiClass createClassInner(@NonNls final String type, @NonNls String name) {
    PsiUtil.checkIsIdentifier(myManager, name);
    final PsiJavaFile aFile = createDummyJavaFile("public " + type + " " + name + " { }");
    final PsiClass[] classes = aFile.getClasses();
    if (classes.length != 1) {
      throw new IncorrectOperationException("Incorrect " + type + " name \"" + name + "\".");
    }
    return classes[0];
  }

  @NotNull
  @Override
  public PsiTypeElement createTypeElement(@NotNull final PsiType psiType) {
    final LightTypeElement element = new LightTypeElement(myManager, psiType);
    CodeEditUtil.setNodeGenerated(element.getNode(), true);
    return element;
  }

  @NotNull
  @Override
  public PsiJavaCodeReferenceElement createReferenceElementByType(
      @NotNull final PsiClassType type) {
    if (type instanceof PsiClassReferenceType) {
      return ((PsiClassReferenceType) type).getReference();
    }
    return new LightClassTypeReference(myManager, type);
  }

  @NotNull
  @Override
  public PsiTypeParameterList createTypeParameterList() {
    final PsiTypeParameterList parameterList =
        createMethodFromText("void foo()", null).getTypeParameterList();
    assert parameterList != null;
    return parameterList;
  }

  @NotNull
  @Override
  public PsiTypeParameter createTypeParameter(String name, PsiClassType[] superTypes) {
    @NonNls StringBuilder builder = new StringBuilder();
    builder.append("public <").append(name);
    if (superTypes.length > 1
        || superTypes.length == 1
            && !superTypes[0].equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
      builder.append(" extends ");
      for (PsiClassType type : superTypes) {
        if (type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) continue;
        builder.append(type.getCanonicalText(true)).append('&');
      }

      builder.delete(builder.length() - 1, builder.length());
    }
    builder.append("> void foo(){}");
    try {
      return createMethodFromText(builder.toString(), null).getTypeParameters()[0];
    } catch (RuntimeException e) {
      throw new IncorrectOperationException("type parameter text: " + builder.toString());
    }
  }

  @NotNull
  @Override
  public PsiField createField(@NotNull final String name, @NotNull final PsiType type)
      throws IncorrectOperationException {
    PsiUtil.checkIsIdentifier(myManager, name);
    if (PsiType.NULL.equals(type)) {
      throw new IncorrectOperationException("Cannot create field with type \"null\".");
    }

    @NonNls
    final String text =
        "class _Dummy_ { private " + type.getCanonicalText(true) + " " + name + "; }";
    final PsiJavaFile aFile = createDummyJavaFile(text);
    final PsiClass[] classes = aFile.getClasses();
    if (classes.length < 1) {
      throw new IncorrectOperationException("Class was not created " + text);
    }
    final PsiClass psiClass = classes[0];
    final PsiField[] fields = psiClass.getFields();
    if (fields.length < 1) {
      throw new IncorrectOperationException("Field was not created " + text);
    }
    PsiField field = fields[0];
    field =
        (PsiField)
            JavaCodeStyleManager.getInstance(myManager.getProject()).shortenClassReferences(field);
    return (PsiField) CodeStyleManager.getInstance(myManager.getProject()).reformat(field);
  }

  @NotNull
  @Override
  public PsiMethod createMethod(@NotNull final String name, final PsiType returnType)
      throws IncorrectOperationException {
    PsiUtil.checkIsIdentifier(myManager, name);
    if (PsiType.NULL.equals(returnType)) {
      throw new IncorrectOperationException("Cannot create method with type \"null\".");
    }

    final String canonicalText = returnType.getCanonicalText(true);
    final PsiJavaFile aFile =
        createDummyJavaFile("class _Dummy_ { public " + canonicalText + " " + name + "() {} }");
    final PsiClass[] classes = aFile.getClasses();
    if (classes.length < 1) {
      throw new IncorrectOperationException(
          "Class was not created. Method name: " + name + "; return type: " + canonicalText);
    }
    final PsiMethod[] methods = classes[0].getMethods();
    if (methods.length < 1) {
      throw new IncorrectOperationException(
          "Method was not created. Method name: " + name + "; return type: " + canonicalText);
    }
    PsiMethod method = methods[0];
    method =
        (PsiMethod)
            JavaCodeStyleManager.getInstance(myManager.getProject()).shortenClassReferences(method);
    return (PsiMethod) CodeStyleManager.getInstance(myManager.getProject()).reformat(method);
  }

  @NotNull
  @Override
  public PsiMethod createMethod(
      @NotNull @NonNls String name, PsiType returnType, PsiElement context)
      throws IncorrectOperationException {
    return createMethodFromText(
        "public " + returnType.getCanonicalText(true) + " " + name + "() {}", context);
  }

  @NotNull
  @Override
  public PsiMethod createConstructor() {
    return createConstructor("_Dummy_");
  }

  @NotNull
  @Override
  public PsiMethod createConstructor(@NotNull @NonNls final String name) {
    final PsiJavaFile aFile =
        createDummyJavaFile("class " + name + " { public " + name + "() {} }");
    final PsiMethod method = aFile.getClasses()[0].getMethods()[0];
    return (PsiMethod) CodeStyleManager.getInstance(myManager.getProject()).reformat(method);
  }

  @Override
  public PsiMethod createConstructor(@NotNull @NonNls String name, PsiElement context) {
    return createMethodFromText(name + "() {}", context);
  }

  @NotNull
  @Override
  public PsiClassInitializer createClassInitializer() throws IncorrectOperationException {
    final PsiJavaFile aFile = createDummyJavaFile("class _Dummy_ { {} }");
    final PsiClassInitializer classInitializer = aFile.getClasses()[0].getInitializers()[0];
    return (PsiClassInitializer)
        CodeStyleManager.getInstance(myManager.getProject()).reformat(classInitializer);
  }

  @NotNull
  @Override
  public PsiParameter createParameter(@NotNull final String name, @NotNull final PsiType type)
      throws IncorrectOperationException {
    PsiUtil.checkIsIdentifier(myManager, name);
    if (PsiType.NULL.equals(type)) {
      throw new IncorrectOperationException("Cannot create parameter with type \"null\".");
    }

    final String text = type.getCanonicalText(true) + " " + name;
    PsiParameter parameter = createParameterFromText(text, null);
    final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(myManager.getProject());
    PsiUtil.setModifierProperty(
        parameter,
        PsiModifier.FINAL,
        JavaCodeStyleSettingsFacade.getInstance(myManager.getProject())
            .isGenerateFinalParameters());
    GeneratedMarkerVisitor.markGenerated(parameter);
    parameter =
        (PsiParameter)
            JavaCodeStyleManager.getInstance(myManager.getProject())
                .shortenClassReferences(parameter);
    return (PsiParameter) codeStyleManager.reformat(parameter);
  }

  @Override
  public PsiParameter createParameter(
      @NotNull @NonNls String name, PsiType type, PsiElement context)
      throws IncorrectOperationException {
    final PsiMethod psiMethod =
        createMethodFromText(
            "void f(" + type.getCanonicalText(true) + " " + name + ") {}", context);
    final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
    return parameters[0];
  }

  @NotNull
  @Override
  public PsiCodeBlock createCodeBlock() {
    final PsiCodeBlock block = createCodeBlockFromText("{}", null);
    return (PsiCodeBlock) CodeStyleManager.getInstance(myManager.getProject()).reformat(block);
  }

  @NotNull
  @Override
  public PsiClassType createType(@NotNull final PsiClass aClass) {
    return new PsiImmediateClassType(
        aClass,
        aClass instanceof PsiTypeParameter ? PsiSubstitutor.EMPTY : createRawSubstitutor(aClass));
  }

  @NotNull
  @Override
  public PsiClassType createType(@NotNull final PsiJavaCodeReferenceElement classReference) {
    return new PsiClassReferenceType(classReference, null);
  }

  @NotNull
  @Override
  public PsiClassType createType(@NotNull final PsiClass aClass, final PsiType parameter) {
    final PsiTypeParameter[] typeParameters = aClass.getTypeParameters();
    assert typeParameters.length == 1 : aClass;

    return createType(aClass, PsiSubstitutor.EMPTY.put(typeParameters[0], parameter));
  }

  @NotNull
  @Override
  public PsiClassType createType(@NotNull final PsiClass aClass, final PsiType... parameters) {
    return createType(aClass, PsiSubstitutor.EMPTY.putAll(aClass, parameters));
  }

  @NotNull
  @Override
  public PsiSubstitutor createRawSubstitutor(@NotNull final PsiTypeParameterListOwner owner) {
    Map<PsiTypeParameter, PsiType> substitutorMap = null;
    for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(owner)) {
      if (substitutorMap == null) substitutorMap = new HashMap<PsiTypeParameter, PsiType>();
      substitutorMap.put(parameter, null);
    }
    return PsiSubstitutorImpl.createSubstitutor(substitutorMap);
  }

  @NotNull
  @Override
  public PsiSubstitutor createRawSubstitutor(
      @NotNull final PsiSubstitutor baseSubstitutor,
      @NotNull final PsiTypeParameter[] typeParameters) {
    Map<PsiTypeParameter, PsiType> substitutorMap = null;
    for (PsiTypeParameter parameter : typeParameters) {
      if (substitutorMap == null) substitutorMap = new HashMap<PsiTypeParameter, PsiType>();
      substitutorMap.put(parameter, null);
    }
    return PsiSubstitutorImpl.createSubstitutor(substitutorMap).putAll(baseSubstitutor);
  }

  @NotNull
  @Override
  public PsiElement createDummyHolder(
      @NotNull final String text,
      @NotNull final IElementType type,
      @Nullable final PsiElement context) {
    final DummyHolder result = DummyHolderFactory.createHolder(myManager, context);
    final FileElement holder = result.getTreeElement();
    final Language language = type.getLanguage();
    final ParserDefinition parserDefinition =
        LanguageParserDefinitions.INSTANCE.forLanguage(language);
    assert parserDefinition != null : "No parser definition for language " + language;
    final Project project = myManager.getProject();
    final Lexer lexer = parserDefinition.createLexer(project);
    final PsiBuilder builder =
        PsiBuilderFactory.getInstance().createBuilder(project, holder, lexer, language, text);
    final ASTNode node = parserDefinition.createParser(project).parse(type, builder);
    holder.rawAddChildren((TreeElement) node);
    final PsiElement psi = node.getPsi();
    assert psi != null : text;
    return psi;
  }

  @NotNull
  @Override
  public PsiSubstitutor createSubstitutor(@NotNull final Map<PsiTypeParameter, PsiType> map) {
    return PsiSubstitutorImpl.createSubstitutor(map);
  }

  @Nullable
  @Override
  public PsiPrimitiveType createPrimitiveType(@NotNull final String text) {
    return PsiJavaParserFacadeImpl.getPrimitiveType(text);
  }

  @NotNull
  @Override
  public PsiClassType createTypeByFQClassName(@NotNull final String qName) {
    return createTypeByFQClassName(qName, GlobalSearchScope.allScope(myManager.getProject()));
  }

  @NotNull
  @Override
  public PsiClassType createTypeByFQClassName(
      @NotNull final String qName, @NotNull final GlobalSearchScope resolveScope) {
    if (CommonClassNames.JAVA_LANG_OBJECT.equals(qName)) {
      PsiClassType cachedObjectType = myCachedObjectType.get(resolveScope);
      if (cachedObjectType != null) {
        return cachedObjectType;
      }
      PsiClass aClass =
          JavaPsiFacade.getInstance(myManager.getProject())
              .findClass(CommonClassNames.JAVA_LANG_OBJECT, resolveScope);
      if (aClass != null) {
        cachedObjectType = new PsiImmediateClassType(aClass, PsiSubstitutor.EMPTY);
        cachedObjectType =
            ConcurrencyUtil.cacheOrGet(myCachedObjectType, resolveScope, cachedObjectType);
        return cachedObjectType;
      }
    }
    return new PsiClassReferenceType(
        createReferenceElementByFQClassName(qName, resolveScope), null);
  }

  @NotNull
  @Override
  public PsiJavaCodeReferenceElement createClassReferenceElement(@NotNull final PsiClass aClass) {
    final String text;
    if (aClass instanceof PsiAnonymousClass) {
      text = ((PsiAnonymousClass) aClass).getBaseClassType().getPresentableText();
    } else {
      text = aClass.getName();
    }
    if (text == null) {
      throw new IncorrectOperationException("Invalid class: " + aClass);
    }
    return new LightClassReference(myManager, text, aClass);
  }

  @NotNull
  @Override
  public PsiJavaCodeReferenceElement createReferenceElementByFQClassName(
      @NotNull final String qName, @NotNull final GlobalSearchScope resolveScope) {
    final String shortName = PsiNameHelper.getShortClassName(qName);
    return new LightClassReference(myManager, shortName, qName, resolveScope);
  }

  @NotNull
  @Override
  public PsiJavaCodeReferenceElement createFQClassNameReferenceElement(
      @NotNull final String qName, @NotNull final GlobalSearchScope resolveScope) {
    return new LightClassReference(myManager, qName, qName, resolveScope);
  }

  @NotNull
  @Override
  public PsiJavaCodeReferenceElement createPackageReferenceElement(
      @NotNull final PsiPackage aPackage) throws IncorrectOperationException {
    if (aPackage.getQualifiedName().isEmpty()) {
      throw new IncorrectOperationException("Cannot create reference to default package.");
    }
    return new LightPackageReference(myManager, aPackage);
  }

  @NotNull
  @Override
  public PsiPackageStatement createPackageStatement(@NotNull final String name)
      throws IncorrectOperationException {
    final PsiJavaFile aFile = createDummyJavaFile("package " + name + ";");
    final PsiPackageStatement stmt = aFile.getPackageStatement();
    if (stmt == null) {
      throw new IncorrectOperationException("Incorrect package name: " + name);
    }
    return stmt;
  }

  @NotNull
  @Override
  public PsiImportStaticStatement createImportStaticStatement(
      @NotNull final PsiClass aClass, @NotNull final String memberName)
      throws IncorrectOperationException {
    if (aClass instanceof PsiAnonymousClass) {
      throw new IncorrectOperationException("Cannot create import statement for anonymous class.");
    } else if (aClass.getParent() instanceof PsiDeclarationStatement) {
      throw new IncorrectOperationException("Cannot create import statement for local class.");
    }

    final PsiJavaFile aFile =
        createDummyJavaFile("import static " + aClass.getQualifiedName() + "." + memberName + ";");
    final PsiImportStatementBase statement = extractImport(aFile, true);
    return (PsiImportStaticStatement)
        CodeStyleManager.getInstance(myManager.getProject()).reformat(statement);
  }

  @NotNull
  @Override
  public PsiParameterList createParameterList(
      @NotNull final String[] names, @NotNull final PsiType[] types)
      throws IncorrectOperationException {
    @NonNls StringBuilder builder = new StringBuilder();
    builder.append("void method(");
    for (int i = 0; i < names.length; i++) {
      if (i > 0) builder.append(", ");
      builder.append(types[i].getCanonicalText(true)).append(' ').append(names[i]);
    }
    builder.append(");");
    return createMethodFromText(builder.toString(), null).getParameterList();
  }

  @NotNull
  @Override
  public PsiReferenceList createReferenceList(
      @NotNull final PsiJavaCodeReferenceElement[] references) throws IncorrectOperationException {
    @NonNls final StringBuilder builder = new StringBuilder();
    builder.append("void method()");
    if (references.length > 0) {
      builder.append(" throws ");
      for (int i = 0; i < references.length; i++) {
        if (i > 0) builder.append(", ");
        builder.append(references[i].getCanonicalText());
      }
    }
    builder.append(';');
    return createMethodFromText(builder.toString(), null).getThrowsList();
  }

  @NotNull
  @Override
  public PsiJavaCodeReferenceElement createPackageReferenceElement(
      @NotNull final String packageName) throws IncorrectOperationException {
    if (packageName.isEmpty()) {
      throw new IncorrectOperationException("Cannot create reference to default package.");
    }
    return new LightPackageReference(myManager, packageName);
  }

  @NotNull
  @Override
  public PsiReferenceExpression createReferenceExpression(@NotNull final PsiClass aClass)
      throws IncorrectOperationException {
    final String text;
    if (aClass instanceof PsiAnonymousClass) {
      text = ((PsiAnonymousClass) aClass).getBaseClassType().getPresentableText();
    } else {
      text = aClass.getName();
    }
    return new LightClassReferenceExpression(myManager, text, aClass);
  }

  @NotNull
  @Override
  public PsiReferenceExpression createReferenceExpression(@NotNull final PsiPackage aPackage)
      throws IncorrectOperationException {
    if (aPackage.getQualifiedName().isEmpty()) {
      throw new IncorrectOperationException("Cannot create reference to default package.");
    }
    return new LightPackageReferenceExpression(myManager, aPackage);
  }

  @NotNull
  @Override
  public PsiIdentifier createIdentifier(@NotNull final String text)
      throws IncorrectOperationException {
    PsiUtil.checkIsIdentifier(myManager, text);
    return new LightIdentifier(myManager, text);
  }

  @NotNull
  @Override
  public PsiKeyword createKeyword(@NotNull final String text) throws IncorrectOperationException {
    if (!PsiNameHelper.getInstance(myManager.getProject()).isKeyword(text)) {
      throw new IncorrectOperationException("\"" + text + "\" is not a keyword.");
    }
    return new LightKeyword(myManager, text);
  }

  @NotNull
  @Override
  public PsiKeyword createKeyword(@NotNull @NonNls String keyword, PsiElement context)
      throws IncorrectOperationException {
    LanguageLevel level = PsiUtil.getLanguageLevel(context);
    if (!JavaLexer.isKeyword(keyword, level) && !JavaLexer.isSoftKeyword(keyword, level)) {
      throw new IncorrectOperationException("\"" + keyword + "\" is not a keyword.");
    }
    return new LightKeyword(myManager, keyword);
  }

  @NotNull
  @Override
  public PsiImportStatement createImportStatement(@NotNull final PsiClass aClass)
      throws IncorrectOperationException {
    if (aClass instanceof PsiAnonymousClass) {
      throw new IncorrectOperationException("Cannot create import statement for anonymous class.");
    } else if (aClass.getParent() instanceof PsiDeclarationStatement) {
      throw new IncorrectOperationException("Cannot create import statement for local class.");
    }

    final PsiJavaFile aFile = createDummyJavaFile("import " + aClass.getQualifiedName() + ";");
    final PsiImportStatementBase statement = extractImport(aFile, false);
    return (PsiImportStatement)
        CodeStyleManager.getInstance(myManager.getProject()).reformat(statement);
  }

  @NotNull
  @Override
  public PsiImportStatement createImportStatementOnDemand(@NotNull final String packageName)
      throws IncorrectOperationException {
    if (packageName.isEmpty()) {
      throw new IncorrectOperationException("Cannot create import statement for default package.");
    }
    if (!PsiNameHelper.getInstance(myManager.getProject()).isQualifiedName(packageName)) {
      throw new IncorrectOperationException("Incorrect package name: \"" + packageName + "\".");
    }

    final PsiJavaFile aFile = createDummyJavaFile("import " + packageName + ".*;");
    final PsiImportStatementBase statement = extractImport(aFile, false);
    return (PsiImportStatement)
        CodeStyleManager.getInstance(myManager.getProject()).reformat(statement);
  }

  @NotNull
  @Override
  public PsiDeclarationStatement createVariableDeclarationStatement(
      @NonNls @NotNull String name, @NotNull PsiType type, @Nullable PsiExpression initializer)
      throws IncorrectOperationException {
    return createVariableDeclarationStatement(name, type, initializer, null);
  }

  @NotNull
  @Override
  public PsiDeclarationStatement createVariableDeclarationStatement(
      @NonNls @NotNull String name,
      @NotNull PsiType type,
      @Nullable PsiExpression initializer,
      @Nullable PsiElement context)
      throws IncorrectOperationException {
    if (!isIdentifier(name)) {
      throw new IncorrectOperationException("\"" + name + "\" is not an identifier.");
    }
    if (PsiType.NULL.equals(type)) {
      throw new IncorrectOperationException("Cannot create variable with type \"null\".");
    }

    String text = "X " + name + (initializer != null ? " = x" : "") + ";";
    PsiDeclarationStatement statement =
        (PsiDeclarationStatement) createStatementFromText(text, context);

    PsiVariable variable = (PsiVariable) statement.getDeclaredElements()[0];
    replace(variable.getTypeElement(), createTypeElement(type), text);

    boolean generateFinalLocals =
        JavaCodeStyleSettingsFacade.getInstance(myManager.getProject()).isGenerateFinalLocals();
    PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, generateFinalLocals);

    if (initializer != null) {
      replace(variable.getInitializer(), initializer, text);
    }

    GeneratedMarkerVisitor.markGenerated(statement);
    return statement;
  }

  private static void replace(
      @Nullable PsiElement original, @NotNull PsiElement replacement, @NotNull String message) {
    assert original != null : message;
    original.replace(replacement);
  }

  @NotNull
  @Override
  public PsiDocTag createParamTag(
      @NotNull final String parameterName, @NonNls final String description)
      throws IncorrectOperationException {
    @NonNls final StringBuilder builder = new StringBuilder();
    builder.append(" * @param ");
    builder.append(parameterName);
    builder.append(" ");
    final String[] strings = description.split("\\n");
    for (int i = 0; i < strings.length; i++) {
      if (i > 0) builder.append("\n * ");
      builder.append(strings[i]);
    }
    return createDocTagFromText(builder.toString());
  }

  @NotNull
  @Override
  public PsiAnnotation createAnnotationFromText(
      @NotNull final String annotationText, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiAnnotation psiAnnotation = super.createAnnotationFromText(annotationText, context);
    GeneratedMarkerVisitor.markGenerated(psiAnnotation);
    return psiAnnotation;
  }

  @NotNull
  @Override
  public PsiCodeBlock createCodeBlockFromText(
      @NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiCodeBlock psiCodeBlock = super.createCodeBlockFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(psiCodeBlock);
    return psiCodeBlock;
  }

  @NotNull
  @Override
  public PsiEnumConstant createEnumConstantFromText(
      @NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiEnumConstant enumConstant = super.createEnumConstantFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(enumConstant);
    return enumConstant;
  }

  @NotNull
  @Override
  public PsiExpression createExpressionFromText(
      @NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiExpression expression = super.createExpressionFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(expression);
    return expression;
  }

  @NotNull
  @Override
  public PsiField createFieldFromText(
      @NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiField psiField = super.createFieldFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(psiField);
    return psiField;
  }

  @NotNull
  @Override
  public PsiParameter createParameterFromText(
      @NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiParameter parameter = super.createParameterFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(parameter);
    return parameter;
  }

  @NotNull
  @Override
  public PsiStatement createStatementFromText(
      @NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    final PsiStatement statement = super.createStatementFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(statement);
    return statement;
  }

  @NotNull
  @Override
  public PsiType createTypeFromText(@NotNull final String text, @Nullable final PsiElement context)
      throws IncorrectOperationException {
    return createTypeInner(text, context, true);
  }

  @NotNull
  @Override
  public PsiTypeParameter createTypeParameterFromText(
      @NotNull final String text, final PsiElement context) throws IncorrectOperationException {
    final PsiTypeParameter typeParameter = super.createTypeParameterFromText(text, context);
    GeneratedMarkerVisitor.markGenerated(typeParameter);
    return typeParameter;
  }

  @NotNull
  @Override
  public PsiMethod createMethodFromText(
      @NotNull final String text, final PsiElement context, final LanguageLevel level)
      throws IncorrectOperationException {
    final PsiMethod method = super.createMethodFromText(text, context, level);
    GeneratedMarkerVisitor.markGenerated(method);
    return method;
  }

  private static PsiImportStatementBase extractImport(
      final PsiJavaFile aFile, final boolean isStatic) {
    final PsiImportList importList = aFile.getImportList();
    assert importList != null : aFile;
    final PsiImportStatementBase[] statements =
        isStatic ? importList.getImportStaticStatements() : importList.getImportStatements();
    assert statements.length == 1 : aFile.getText();
    return statements[0];
  }

  private static final JavaParserUtil.ParserWrapper CATCH_SECTION =
      new JavaParserUtil.ParserWrapper() {
        @Override
        public void parse(final PsiBuilder builder) {
          JavaParser.INSTANCE.getStatementParser().parseCatchBlock(builder);
        }
      };

  @NotNull
  @Override
  public PsiCatchSection createCatchSection(
      @NotNull final PsiType exceptionType,
      @NotNull final String exceptionName,
      @Nullable final PsiElement context)
      throws IncorrectOperationException {
    if (!(exceptionType instanceof PsiClassType || exceptionType instanceof PsiDisjunctionType)) {
      throw new IncorrectOperationException("Unexpected type:" + exceptionType);
    }

    @NonNls
    final String text =
        "catch (" + exceptionType.getCanonicalText(true) + " " + exceptionName + ") {}";
    final DummyHolder holder =
        DummyHolderFactory.createHolder(
            myManager, new JavaDummyElement(text, CATCH_SECTION, level(context)), context);
    final PsiElement element =
        SourceTreeToPsiMap.treeElementToPsi(holder.getTreeElement().getFirstChildNode());
    if (!(element instanceof PsiCatchSection)) {
      throw new IncorrectOperationException(
          "Incorrect catch section '" + text + "'. Parsed element: " + element);
    }

    final Project project = myManager.getProject();
    final JavaPsiImplementationHelper helper = JavaPsiImplementationHelper.getInstance(project);
    helper.setupCatchBlock(exceptionName, exceptionType, context, (PsiCatchSection) element);
    final CodeStyleManager styleManager = CodeStyleManager.getInstance(project);
    final PsiCatchSection catchSection = (PsiCatchSection) styleManager.reformat(element);

    GeneratedMarkerVisitor.markGenerated(catchSection);
    return catchSection;
  }

  @Override
  public boolean isValidClassName(@NotNull String name) {
    return isIdentifier(name);
  }

  @Override
  public boolean isValidMethodName(@NotNull String name) {
    return isIdentifier(name);
  }

  @Override
  public boolean isValidParameterName(@NotNull String name) {
    return isIdentifier(name);
  }

  @Override
  public boolean isValidFieldName(@NotNull String name) {
    return isIdentifier(name);
  }

  @Override
  public boolean isValidLocalVariableName(@NotNull String name) {
    return isIdentifier(name);
  }

  private boolean isIdentifier(@NotNull String name) {
    return PsiNameHelper.getInstance(myManager.getProject()).isIdentifier(name);
  }
}
Example #12
0
/** User: anna Date: 24-Feb-2006 */
public class SeverityRegistrar implements JDOMExternalizable, Comparator<HighlightSeverity> {
  @NonNls private static final String INFO_TAG = "info";
  @NonNls private static final String COLOR_ATTRIBUTE = "color";
  private final Map<String, SeverityBasedTextAttributes> myMap = ContainerUtil.newConcurrentMap();
  private final Map<String, Color> myRendererColors = ContainerUtil.newConcurrentMap();
  public static final Topic<Runnable> SEVERITIES_CHANGED_TOPIC =
      Topic.create("SEVERITIES_CHANGED_TOPIC", Runnable.class, Topic.BroadcastDirection.TO_PARENT);
  @NotNull private final MessageBus myMessageBus;

  private volatile OrderMap myOrderMap;
  private JDOMExternalizableStringList myReadOrder;

  private static final Map<String, HighlightInfoType> STANDARD_SEVERITIES =
      ContainerUtil.newConcurrentMap();

  public SeverityRegistrar(@NotNull MessageBus messageBus) {
    myMessageBus = messageBus;
  }

  static {
    registerStandard(HighlightInfoType.ERROR, HighlightSeverity.ERROR);
    registerStandard(HighlightInfoType.WARNING, HighlightSeverity.WARNING);
    registerStandard(HighlightInfoType.INFO, HighlightSeverity.INFO);
    registerStandard(HighlightInfoType.WEAK_WARNING, HighlightSeverity.WEAK_WARNING);
    registerStandard(
        HighlightInfoType.GENERIC_WARNINGS_OR_ERRORS_FROM_SERVER,
        HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING);
  }

  public static void registerStandard(
      @NotNull HighlightInfoType highlightInfoType, @NotNull HighlightSeverity highlightSeverity) {
    STANDARD_SEVERITIES.put(highlightSeverity.getName(), highlightInfoType);
  }

  @NotNull
  public static SeverityRegistrar getSeverityRegistrar(@Nullable Project project) {
    return project == null
        ? InspectionProfileManager.getInstance().getSeverityRegistrar()
        : InspectionProjectProfileManager.getInstance(project).getSeverityRegistrar();
  }

  public void registerSeverity(@NotNull SeverityBasedTextAttributes info, Color renderColor) {
    final HighlightSeverity severity = info.getType().getSeverity(null);
    myMap.put(severity.getName(), info);
    if (renderColor != null) {
      myRendererColors.put(severity.getName(), renderColor);
    }
    myOrderMap = null;
    HighlightDisplayLevel.registerSeverity(
        severity, getHighlightInfoTypeBySeverity(severity).getAttributesKey());
    severitiesChanged();
  }

  private void severitiesChanged() {
    myMessageBus.syncPublisher(SEVERITIES_CHANGED_TOPIC).run();
  }

  public SeverityBasedTextAttributes unregisterSeverity(@NotNull HighlightSeverity severity) {
    return myMap.remove(severity.getName());
  }

  @NotNull
  public HighlightInfoType.HighlightInfoTypeImpl getHighlightInfoTypeBySeverity(
      @NotNull HighlightSeverity severity) {
    HighlightInfoType infoType = STANDARD_SEVERITIES.get(severity.getName());
    if (infoType != null) {
      return (HighlightInfoType.HighlightInfoTypeImpl) infoType;
    }

    if (severity == HighlightSeverity.INFORMATION) {
      return (HighlightInfoType.HighlightInfoTypeImpl) HighlightInfoType.INFORMATION;
    }

    final SeverityBasedTextAttributes type = getAttributesBySeverity(severity);
    return (HighlightInfoType.HighlightInfoTypeImpl)
        (type == null ? HighlightInfoType.WARNING : type.getType());
  }

  private SeverityBasedTextAttributes getAttributesBySeverity(@NotNull HighlightSeverity severity) {
    return myMap.get(severity.getName());
  }

  @Nullable
  public TextAttributes getTextAttributesBySeverity(@NotNull HighlightSeverity severity) {
    final SeverityBasedTextAttributes infoType = getAttributesBySeverity(severity);
    if (infoType != null) {
      return infoType.getAttributes();
    }
    return null;
  }

  @Override
  public void readExternal(Element element) throws InvalidDataException {
    myMap.clear();
    myRendererColors.clear();
    final List children = element.getChildren(INFO_TAG);
    for (Object child : children) {
      final Element infoElement = (Element) child;

      final SeverityBasedTextAttributes highlightInfo =
          new SeverityBasedTextAttributes(infoElement);

      Color color = null;
      final String colorStr = infoElement.getAttributeValue(COLOR_ATTRIBUTE);
      if (colorStr != null) {
        color = new Color(Integer.parseInt(colorStr, 16));
      }
      registerSeverity(highlightInfo, color);
    }
    myReadOrder = new JDOMExternalizableStringList();
    myReadOrder.readExternal(element);
    List<HighlightSeverity> read = new ArrayList<HighlightSeverity>(myReadOrder.size());
    final List<HighlightSeverity> knownSeverities = getDefaultOrder();
    for (String name : myReadOrder) {
      HighlightSeverity severity = getSeverity(name);
      if (severity == null || !knownSeverities.contains(severity)) continue;
      read.add(severity);
    }
    OrderMap orderMap = fromList(read);
    if (orderMap.isEmpty()) {
      orderMap = fromList(knownSeverities);
    } else {
      // enforce include all known
      List<HighlightSeverity> list = getOrderAsList(orderMap);
      for (int i = 0; i < knownSeverities.size(); i++) {
        HighlightSeverity stdSeverity = knownSeverities.get(i);
        if (!list.contains(stdSeverity)) {
          for (int oIdx = 0; oIdx < list.size(); oIdx++) {
            HighlightSeverity orderSeverity = list.get(oIdx);
            HighlightInfoType type = STANDARD_SEVERITIES.get(orderSeverity.getName());
            if (type != null && knownSeverities.indexOf(type.getSeverity(null)) > i) {
              list.add(oIdx, stdSeverity);
              myReadOrder = null;
              break;
            }
          }
        }
      }
      orderMap = fromList(list);
    }
    myOrderMap = orderMap;
    severitiesChanged();
  }

  @Override
  public void writeExternal(Element element) throws WriteExternalException {
    List<HighlightSeverity> list = getOrderAsList(getOrderMap());
    for (HighlightSeverity severity : list) {
      Element info = new Element(INFO_TAG);
      String severityName = severity.getName();
      final SeverityBasedTextAttributes infoType = getAttributesBySeverity(severity);
      if (infoType != null) {
        infoType.writeExternal(info);
        final Color color = myRendererColors.get(severityName);
        if (color != null) {
          info.setAttribute(COLOR_ATTRIBUTE, Integer.toString(color.getRGB() & 0xFFFFFF, 16));
        }
        element.addContent(info);
      }
    }

    if (myReadOrder != null && !myReadOrder.isEmpty()) {
      myReadOrder.writeExternal(element);
    } else if (!getDefaultOrder().equals(list)) {
      final JDOMExternalizableStringList ext =
          new JDOMExternalizableStringList(Collections.nCopies(getOrderMap().size(), ""));
      getOrderMap()
          .forEachEntry(
              new TObjectIntProcedure<HighlightSeverity>() {
                @Override
                public boolean execute(HighlightSeverity orderSeverity, int oIdx) {
                  ext.set(oIdx, orderSeverity.getName());
                  return true;
                }
              });
      ext.writeExternal(element);
    }
  }

  @NotNull
  private static List<HighlightSeverity> getOrderAsList(@NotNull final OrderMap orderMap) {
    List<HighlightSeverity> list = new ArrayList<HighlightSeverity>();
    for (Object o : orderMap.keys()) {
      list.add((HighlightSeverity) o);
    }
    Collections.sort(
        list,
        new Comparator<HighlightSeverity>() {
          @Override
          public int compare(HighlightSeverity o1, HighlightSeverity o2) {
            return SeverityRegistrar.compare(o1, o2, orderMap);
          }
        });
    return list;
  }

  public int getSeveritiesCount() {
    return createCurrentSeverityNames().size();
  }

  public HighlightSeverity getSeverityByIndex(final int i) {
    final HighlightSeverity[] found = new HighlightSeverity[1];
    getOrderMap()
        .forEachEntry(
            new TObjectIntProcedure<HighlightSeverity>() {
              @Override
              public boolean execute(HighlightSeverity severity, int order) {
                if (order == i) {
                  found[0] = severity;
                  return false;
                }
                return true;
              }
            });
    return found[0];
  }

  public int getSeverityMaxIndex() {
    int[] values = getOrderMap().getValues();
    int max = values[0];
    for (int i = 1; i < values.length; ++i) if (values[i] > max) max = values[i];

    return max;
  }

  @Nullable
  public HighlightSeverity getSeverity(@NotNull String name) {
    final HighlightInfoType type = STANDARD_SEVERITIES.get(name);
    if (type != null) return type.getSeverity(null);
    final SeverityBasedTextAttributes attributes = myMap.get(name);
    if (attributes != null) return attributes.getSeverity();
    return null;
  }

  @NotNull
  private List<String> createCurrentSeverityNames() {
    List<String> list = new ArrayList<String>();
    list.addAll(STANDARD_SEVERITIES.keySet());
    list.addAll(myMap.keySet());
    ContainerUtil.sort(list);
    return list;
  }

  public Icon getRendererIconByIndex(int i) {
    final HighlightSeverity severity = getSeverityByIndex(i);
    HighlightDisplayLevel level = HighlightDisplayLevel.find(severity);
    if (level != null) {
      return level.getIcon();
    }

    return HighlightDisplayLevel.createIconByMask(myRendererColors.get(severity.getName()));
  }

  public boolean isSeverityValid(@NotNull String severityName) {
    return createCurrentSeverityNames().contains(severityName);
  }

  @Override
  public int compare(final HighlightSeverity s1, final HighlightSeverity s2) {
    return compare(s1, s2, getOrderMap());
  }

  private static int compare(HighlightSeverity s1, HighlightSeverity s2, OrderMap orderMap) {
    int o1 = orderMap.getOrder(s1, -1);
    int o2 = orderMap.getOrder(s2, -1);
    return o1 - o2;
  }

  @NotNull
  private OrderMap getOrderMap() {
    OrderMap orderMap;
    OrderMap defaultOrder = null;
    while ((orderMap = myOrderMap) == null) {
      if (defaultOrder == null) {
        defaultOrder = fromList(getDefaultOrder());
      }
      boolean replaced = ORDER_MAP_UPDATER.compareAndSet(this, null, defaultOrder);
      if (replaced) {
        orderMap = defaultOrder;
        break;
      }
    }
    return orderMap;
  }

  private static final AtomicFieldUpdater<SeverityRegistrar, OrderMap> ORDER_MAP_UPDATER =
      AtomicFieldUpdater.forFieldOfType(SeverityRegistrar.class, OrderMap.class);

  @NotNull
  private static OrderMap fromList(@NotNull List<HighlightSeverity> orderList) {
    TObjectIntHashMap<HighlightSeverity> map = new TObjectIntHashMap<HighlightSeverity>();
    for (int i = 0; i < orderList.size(); i++) {
      HighlightSeverity severity = orderList.get(i);
      map.put(severity, i);
    }
    return new OrderMap(map);
  }

  @NotNull
  private List<HighlightSeverity> getDefaultOrder() {
    Collection<SeverityBasedTextAttributes> values = myMap.values();
    List<HighlightSeverity> order =
        new ArrayList<HighlightSeverity>(STANDARD_SEVERITIES.size() + values.size());
    for (HighlightInfoType type : STANDARD_SEVERITIES.values()) {
      order.add(type.getSeverity(null));
    }
    for (SeverityBasedTextAttributes attributes : values) {
      order.add(attributes.getSeverity());
    }
    ContainerUtil.sort(order);
    return order;
  }

  public void setOrder(@NotNull List<HighlightSeverity> orderList) {
    myOrderMap = fromList(orderList);
    myReadOrder = null;
    severitiesChanged();
  }

  public int getSeverityIdx(@NotNull HighlightSeverity severity) {
    return getOrderMap().getOrder(severity, -1);
  }

  public boolean isDefaultSeverity(@NotNull HighlightSeverity severity) {
    return STANDARD_SEVERITIES.containsKey(severity.myName);
  }

  public static boolean isGotoBySeverityEnabled(@NotNull HighlightSeverity minSeverity) {
    for (SeveritiesProvider provider : Extensions.getExtensions(SeveritiesProvider.EP_NAME)) {
      if (provider.isGotoBySeverityEnabled(minSeverity)) return true;
    }
    return minSeverity != HighlightSeverity.INFORMATION;
  }

  private static class OrderMap extends TObjectIntHashMap<HighlightSeverity> {
    private OrderMap(@NotNull TObjectIntHashMap<HighlightSeverity> map) {
      super(map.size());
      map.forEachEntry(
          new TObjectIntProcedure<HighlightSeverity>() {
            @Override
            public boolean execute(HighlightSeverity key, int value) {
              OrderMap.super.put(key, value);
              return true;
            }
          });
      trimToSize();
    }

    private int getOrder(@NotNull HighlightSeverity severity, int defaultOrder) {
      int index = index(severity);
      return index < 0 ? defaultOrder : _values[index];
    }

    @Override
    public void clear() {
      throw new IncorrectOperationException("readonly");
    }

    @Override
    protected void removeAt(int index) {
      throw new IncorrectOperationException("readonly");
    }

    @Override
    public void transformValues(TIntFunction function) {
      throw new IncorrectOperationException("readonly");
    }

    @Override
    public boolean adjustValue(HighlightSeverity key, int amount) {
      throw new IncorrectOperationException("readonly");
    }

    @Override
    public int put(HighlightSeverity key, int value) {
      throw new IncorrectOperationException("readonly");
    }

    @Override
    public int remove(HighlightSeverity key) {
      throw new IncorrectOperationException("readonly");
    }
  }

  public static class SeverityBasedTextAttributes {
    private final TextAttributes myAttributes;
    private final HighlightInfoType.HighlightInfoTypeImpl myType;

    // read external
    public SeverityBasedTextAttributes(@NotNull Element element) throws InvalidDataException {
      this(new TextAttributes(element), new HighlightInfoType.HighlightInfoTypeImpl(element));
    }

    public SeverityBasedTextAttributes(
        @NotNull TextAttributes attributes, @NotNull HighlightInfoType.HighlightInfoTypeImpl type) {
      myAttributes = attributes;
      myType = type;
    }

    @NotNull
    public TextAttributes getAttributes() {
      return myAttributes;
    }

    @NotNull
    public HighlightInfoType.HighlightInfoTypeImpl getType() {
      return myType;
    }

    private void writeExternal(@NotNull Element element) throws WriteExternalException {
      myAttributes.writeExternal(element);
      myType.writeExternal(element);
    }

    @NotNull
    public HighlightSeverity getSeverity() {
      return myType.getSeverity(null);
    }

    @Override
    public boolean equals(final Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      final SeverityBasedTextAttributes that = (SeverityBasedTextAttributes) o;

      if (!myAttributes.equals(that.myAttributes)) return false;
      if (!myType.equals(that.myType)) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = myAttributes.hashCode();
      result = 31 * result + myType.hashCode();
      return result;
    }
  }

  @NotNull
  Collection<SeverityBasedTextAttributes> allRegisteredAttributes() {
    return new ArrayList<SeverityBasedTextAttributes>(myMap.values());
  }

  @NotNull
  Collection<HighlightInfoType> standardSeverities() {
    return STANDARD_SEVERITIES.values();
  }
}
public class VcsLogDataHolder implements Disposable, VcsLogDataProvider {

  private static final Logger LOG = Logger.getInstance(VcsLogDataHolder.class);

  @NotNull private final Project myProject;
  @NotNull private final Map<VirtualFile, VcsLogProvider> myLogProviders;
  @NotNull private final BackgroundTaskQueue myDataLoaderQueue;
  @NotNull private final MiniDetailsGetter myMiniDetailsGetter;
  @NotNull private final CommitDetailsGetter myDetailsGetter;
  @NotNull private final VcsLogSettings mySettings;

  /**
   * Current user name, as specified in the VCS settings. It can be configured differently for
   * different roots => store in a map.
   */
  private final Map<VirtualFile, VcsUser> myCurrentUser = ContainerUtil.newHashMap();

  private final Consumer<DataPack> myDataPackUpdateHandler;

  /**
   * Cached details of the latest commits. We store them separately from the cache of {@link
   * DataGetter}, to make sure that they are always available, which is important because these
   * details will be constantly visible to the user, thus it would be annoying to re-load them from
   * VCS if the cache overflows.
   */
  @NotNull
  private final Map<Integer, VcsCommitMetadata> myTopCommitsDetailsCache =
      ContainerUtil.newConcurrentMap();

  private final VcsUserRegistryImpl myUserRegistry;

  private final VcsLogHashMapImpl myHashMap;
  private final ContainingBranchesGetter myContainingBranchesGetter;

  @NotNull private final VcsLogRefresher myRefresher;
  private final VcsLogFiltererImpl myFilterer;

  public VcsLogDataHolder(
      @NotNull Project project,
      @NotNull Disposable parentDisposable,
      @NotNull Map<VirtualFile, VcsLogProvider> logProviders,
      @NotNull VcsLogSettings settings,
      @NotNull VcsLogUiProperties uiProperties,
      @NotNull Consumer<VisiblePack> visiblePackConsumer) {
    Disposer.register(parentDisposable, this);
    myProject = project;
    myLogProviders = logProviders;
    myDataLoaderQueue = new BackgroundTaskQueue(project, "Loading history...");
    mySettings = settings;
    myUserRegistry =
        (VcsUserRegistryImpl) ServiceManager.getService(project, VcsUserRegistry.class);

    try {
      myHashMap = new VcsLogHashMapImpl(myProject, logProviders);
    } catch (IOException e) {
      throw new RuntimeException(
          e); // TODO: show a message to the user & fallback to using in-memory Hashes
    }
    myMiniDetailsGetter =
        new MiniDetailsGetter(myHashMap, logProviders, myTopCommitsDetailsCache, this);
    myDetailsGetter = new CommitDetailsGetter(myHashMap, logProviders, this);
    myContainingBranchesGetter = new ContainingBranchesGetter(this, this);

    myFilterer =
        new VcsLogFiltererImpl(
            myProject,
            myLogProviders,
            myHashMap,
            myTopCommitsDetailsCache,
            myDetailsGetter,
            PermanentGraph.SortType.values()[uiProperties.getBekSortType()],
            visiblePackConsumer);

    myDataPackUpdateHandler =
        new Consumer<DataPack>() {
          @Override
          public void consume(DataPack dataPack) {
            myFilterer.onRefresh(dataPack);
          }
        };

    myRefresher =
        new VcsLogRefresherImpl(
            myProject,
            myHashMap,
            myLogProviders,
            myUserRegistry,
            myTopCommitsDetailsCache,
            myDataPackUpdateHandler,
            new Consumer<Exception>() {
              @Override
              public void consume(Exception e) {
                if (!(e instanceof ProcessCanceledException)) {
                  LOG.error(e);
                }
              }
            },
            mySettings.getRecentCommitsCount());
  }

  @NotNull
  public VcsLogFilterer getFilterer() {
    return myFilterer;
  }

  @Override
  @NotNull
  public Hash getHash(int commitIndex) {
    return myHashMap.getHash(commitIndex);
  }

  @Override
  public int getCommitIndex(@NotNull Hash hash) {
    return myHashMap.getCommitIndex(hash);
  }

  @NotNull
  public VcsLogHashMapImpl getHashMap() {
    return myHashMap;
  }

  public void initialize() {
    final StopWatch initSw = StopWatch.start("initialize");
    myDataLoaderQueue.clear();

    runInBackground(
        new ThrowableConsumer<ProgressIndicator, VcsException>() {
          @Override
          public void consume(ProgressIndicator indicator) throws VcsException {
            resetState();
            readCurrentUser();
            DataPack dataPack = myRefresher.readFirstBlock();
            myDataPackUpdateHandler.consume(dataPack);
            initSw.report();
          }
        },
        "Loading History...");
  }

  private void readCurrentUser() {
    StopWatch sw = StopWatch.start("readCurrentUser");
    for (Map.Entry<VirtualFile, VcsLogProvider> entry : myLogProviders.entrySet()) {
      VirtualFile root = entry.getKey();
      try {
        VcsUser me = entry.getValue().getCurrentUser(root);
        if (me != null) {
          myCurrentUser.put(root, me);
        } else {
          LOG.info("Username not configured for root " + root);
        }
      } catch (VcsException e) {
        LOG.warn("Couldn't read the username from root " + root, e);
      }
    }
    sw.report();
  }

  private void resetState() {
    myTopCommitsDetailsCache.clear();
  }

  @NotNull
  public Set<VcsUser> getAllUsers() {
    return myUserRegistry.getUsers();
  }

  @NotNull
  public Map<VirtualFile, VcsUser> getCurrentUser() {
    return myCurrentUser;
  }

  public boolean isMultiRoot() {
    return myLogProviders.size() > 1;
  }

  @NotNull
  public Project getProject() {
    return myProject;
  }

  @NotNull
  public Collection<VirtualFile> getRoots() {
    return myLogProviders.keySet();
  }

  @NotNull
  public Collection<VcsLogProvider> getLogProviders() {
    return myLogProviders.values();
  }

  @NotNull
  public VcsLogSettings getSettings() {
    return mySettings;
  }

  public ContainingBranchesGetter getContainingBranchesGetter() {
    return myContainingBranchesGetter;
  }

  private void runInBackground(
      final ThrowableConsumer<ProgressIndicator, VcsException> task, final String title) {
    myDataLoaderQueue.run(
        new Task.Backgroundable(myProject, title, false) {
          @Override
          public void run(@NotNull ProgressIndicator indicator) {
            indicator.setIndeterminate(true);
            try {
              task.consume(indicator);
            } catch (VcsException e) {
              throw new RuntimeException(e); // TODO
            }
          }
        });
  }

  /**
   * Makes the log perform complete refresh for all roots. It fairly retrieves the data from the VCS
   * and rebuilds the whole log.
   */
  public void refreshCompletely() {
    initialize();
  }

  /**
   * Makes the log perform refresh for the given root. This refresh can be optimized, i. e. it can
   * query VCS just for the part of the log.
   */
  public void refresh(@NotNull Collection<VirtualFile> roots) {
    myRefresher.refresh(roots);
  }

  @Nullable
  public VcsCommitMetadata getTopCommitDetails(@NotNull Integer commitId) {
    return myTopCommitsDetailsCache.get(commitId);
  }

  public CommitDetailsGetter getCommitDetailsGetter() {
    return myDetailsGetter;
  }

  @NotNull
  public MiniDetailsGetter getMiniDetailsGetter() {
    return myMiniDetailsGetter;
  }

  @Override
  public void dispose() {
    myDataLoaderQueue.clear();
    resetState();
  }

  @NotNull
  public VcsLogProvider getLogProvider(@NotNull VirtualFile root) {
    return myLogProviders.get(root);
  }

  @NotNull
  public VcsUserRegistryImpl getUserRegistry() {
    return myUserRegistry;
  }
}
/** @author max */
public class InspectionResultsView extends JPanel
    implements Disposable, OccurenceNavigator, DataProvider {
  public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView");

  private final Project myProject;
  private final InspectionTree myTree;
  private final Browser myBrowser;
  private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>>
      myGroups = ContainerUtil.newConcurrentMap();
  private final OccurenceNavigator myOccurenceNavigator;
  private volatile InspectionProfile myInspectionProfile;
  private final AnalysisScope myScope;
  @NonNls private static final String HELP_ID = "reference.toolWindows.inspections";
  private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode>
      mySeverityGroupNodes = ContainerUtil.newConcurrentMap();

  private final Splitter mySplitter;
  @NotNull private final GlobalInspectionContextImpl myGlobalInspectionContext;
  private boolean myRerun;
  private volatile boolean myDisposed;

  @NotNull private final InspectionRVContentProvider myProvider;
  private AnAction myIncludeAction;
  private AnAction myExcludeAction;

  public InspectionResultsView(
      @NotNull final Project project,
      final InspectionProfile inspectionProfile,
      @NotNull AnalysisScope scope,
      @NotNull GlobalInspectionContextImpl globalInspectionContext,
      @NotNull InspectionRVContentProvider provider) {
    setLayout(new BorderLayout());

    myProject = project;
    myInspectionProfile = inspectionProfile;
    myScope = scope;
    myGlobalInspectionContext = globalInspectionContext;
    myProvider = provider;

    myTree = new InspectionTree(project, globalInspectionContext);
    initTreeListeners();

    myOccurenceNavigator = initOccurenceNavigator();

    myBrowser = new Browser(this);

    mySplitter =
        new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION);

    mySplitter.setFirstComponent(
        ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT | SideBorder.RIGHT));
    mySplitter.setSecondComponent(myBrowser);

    mySplitter.addPropertyChangeListener(
        new PropertyChangeListener() {
          @Override
          public void propertyChange(PropertyChangeEvent evt) {
            if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) {
              myGlobalInspectionContext.setSplitterProportion(
                  ((Float) evt.getNewValue()).floatValue());
            }
          }
        });
    add(mySplitter, BorderLayout.CENTER);

    myBrowser.addClickListener(
        new Browser.ClickListener() {
          @Override
          public void referenceClicked(final Browser.ClickEvent e) {
            if (e.getEventType() == Browser.ClickEvent.REF_ELEMENT) {
              final RefElement refElement = e.getClickedElement();
              final OpenFileDescriptor descriptor = getOpenFileDescriptor(refElement);
              if (descriptor != null) {
                FileEditorManager.getInstance(project).openTextEditor(descriptor, false);
              }
            } else if (e.getEventType() == Browser.ClickEvent.FILE_OFFSET) {
              final VirtualFile file = e.getFile();
              final OpenFileDescriptor descriptor =
                  new OpenFileDescriptor(project, file, e.getStartOffset());
              final Editor editor =
                  FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
              if (editor != null) {
                final TextAttributes selectionAttributes =
                    EditorColorsManager.getInstance()
                        .getGlobalScheme()
                        .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
                HighlightManager.getInstance(project)
                    .addRangeHighlight(
                        editor,
                        e.getStartOffset(),
                        e.getEndOffset(),
                        selectionAttributes,
                        true,
                        null);
              }
            }
          }
        });

    createActionsToolbar();
    TreeUtil.selectFirstNode(myTree);
  }

  private void initTreeListeners() {
    myTree
        .getSelectionModel()
        .addTreeSelectionListener(
            new TreeSelectionListener() {
              @Override
              public void valueChanged(TreeSelectionEvent e) {
                syncBrowser();
                if (isAutoScrollMode()) {
                  OpenSourceUtil.openSourcesFrom(
                      DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
                }
              }
            });

    EditSourceOnDoubleClickHandler.install(myTree);

    myTree.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
              OpenSourceUtil.openSourcesFrom(
                  DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
            }
          }
        });

    myTree.addMouseListener(
        new PopupHandler() {
          @Override
          public void invokePopup(Component comp, int x, int y) {
            popupInvoked(comp, x, y);
          }
        });

    SmartExpander.installOn(myTree);
  }

  private OccurenceNavigatorSupport initOccurenceNavigator() {
    return new OccurenceNavigatorSupport(myTree) {
      @Override
      @Nullable
      protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
        if (node instanceof InspectionTreeNode && ((InspectionTreeNode) node).isResolved()) {
          return null;
        }
        if (node instanceof RefElementNode) {
          final RefElementNode refNode = (RefElementNode) node;
          if (refNode.hasDescriptorsUnder()) return null;
          final RefEntity element = refNode.getElement();
          if (element == null || !element.isValid()) return null;
          final CommonProblemDescriptor problem = refNode.getProblem();
          if (problem != null) {
            return navigate(problem);
          }
          if (element instanceof RefElement) {
            return getOpenFileDescriptor((RefElement) element);
          }
        } else if (node instanceof ProblemDescriptionNode) {
          if (!((ProblemDescriptionNode) node).isValid()) return null;
          return navigate(((ProblemDescriptionNode) node).getDescriptor());
        }
        return null;
      }

      @Nullable
      private Navigatable navigate(final CommonProblemDescriptor descriptor) {
        return getSelectedNavigatable(descriptor);
      }

      @Override
      public String getNextOccurenceActionName() {
        return InspectionsBundle.message("inspection.action.go.next");
      }

      @Override
      public String getPreviousOccurenceActionName() {
        return InspectionsBundle.message("inspection.actiongo.prev");
      }
    };
  }

  private void createActionsToolbar() {
    final JComponent leftActionsToolbar = createLeftActionsToolbar();
    final JComponent rightActionsToolbar = createRightActionsToolbar();

    JPanel westPanel = new JPanel(new BorderLayout());
    westPanel.add(leftActionsToolbar, BorderLayout.WEST);
    westPanel.add(rightActionsToolbar, BorderLayout.EAST);
    add(westPanel, BorderLayout.WEST);
  }

  @SuppressWarnings({"NonStaticInitializer"})
  private JComponent createRightActionsToolbar() {
    myIncludeAction =
        new AnAction(InspectionsBundle.message("inspections.result.view.include.action.text")) {
          {
            registerCustomShortcutSet(CommonShortcuts.INSERT, myTree);
          }

          @Override
          public void actionPerformed(AnActionEvent e) {
            final TreePath[] paths = myTree.getSelectionPaths();
            if (paths != null) {
              for (TreePath path : paths) {
                ((InspectionTreeNode) path.getLastPathComponent()).amnesty();
              }
            }
            updateView(false);
          }

          @Override
          public void update(final AnActionEvent e) {
            final TreePath[] paths = myTree.getSelectionPaths();
            e.getPresentation()
                .setEnabled(
                    paths != null
                        && paths.length > 0
                        && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS);
          }
        };

    myExcludeAction =
        new AnAction(InspectionsBundle.message("inspections.result.view.exclude.action.text")) {
          {
            registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree);
          }

          @Override
          public void actionPerformed(final AnActionEvent e) {
            final TreePath[] paths = myTree.getSelectionPaths();
            if (paths != null) {
              for (TreePath path : paths) {
                ((InspectionTreeNode) path.getLastPathComponent()).ignoreElement();
              }
            }
            updateView(false);
          }

          @Override
          public void update(final AnActionEvent e) {
            final TreePath[] path = myTree.getSelectionPaths();
            e.getPresentation().setEnabled(path != null && path.length > 0);
          }
        };

    DefaultActionGroup specialGroup = new DefaultActionGroup();
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this));
    specialGroup.add(
        myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this));
    specialGroup.add(
        myGlobalInspectionContext.getUIOptions().createShowOutdatedProblemsAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createShowDiffOnlyAction(this));
    specialGroup.add(new EditSettingsAction());
    specialGroup.add(new InvokeQuickFixAction(this));
    specialGroup.add(new InspectionsOptionsToolbarAction(this));
    return createToolbar(specialGroup);
  }

  private JComponent createLeftActionsToolbar() {
    final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
    DefaultActionGroup group = new DefaultActionGroup();
    group.add(new RerunAction(this));
    group.add(new CloseAction());
    final TreeExpander treeExpander =
        new TreeExpander() {
          @Override
          public void expandAll() {
            TreeUtil.expandAll(myTree);
          }

          @Override
          public boolean canExpand() {
            return true;
          }

          @Override
          public void collapseAll() {
            TreeUtil.collapseAll(myTree, 0);
          }

          @Override
          public boolean canCollapse() {
            return true;
          }
        };
    group.add(actionsManager.createExpandAllAction(treeExpander, myTree));
    group.add(actionsManager.createCollapseAllAction(treeExpander, myTree));
    group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator()));
    group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator()));
    group.add(myGlobalInspectionContext.createToggleAutoscrollAction());
    group.add(new ExportHTMLAction(this));
    group.add(new ContextHelpAction(HELP_ID));

    return createToolbar(group);
  }

  private static JComponent createToolbar(final DefaultActionGroup specialGroup) {
    return ActionManager.getInstance()
        .createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false)
        .getComponent();
  }

  @Override
  public void dispose() {
    mySplitter.dispose();
    myBrowser.dispose();
    myInspectionProfile = null;
    myDisposed = true;
  }

  private boolean isAutoScrollMode() {
    String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId();
    return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE
        && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION));
  }

  @Nullable
  private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) {
    final VirtualFile[] file = new VirtualFile[1];
    final int[] offset = new int[1];

    ApplicationManager.getApplication()
        .runReadAction(
            new Runnable() {
              @Override
              public void run() {
                PsiElement psiElement = refElement.getElement();
                if (psiElement != null) {
                  final PsiFile containingFile = psiElement.getContainingFile();
                  if (containingFile != null) {
                    file[0] = containingFile.getVirtualFile();
                    offset[0] = psiElement.getTextOffset();
                  }
                } else {
                  file[0] = null;
                }
              }
            });

    if (file[0] != null && file[0].isValid()) {
      return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]);
    }
    return null;
  }

  private void syncBrowser() {
    if (myTree.getSelectionModel().getSelectionCount() != 1) {
      myBrowser.showEmpty();
    } else {
      TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath();
      if (pathSelected != null) {
        final InspectionTreeNode node = (InspectionTreeNode) pathSelected.getLastPathComponent();
        if (node instanceof RefElementNode) {
          final RefElementNode refElementNode = (RefElementNode) node;
          final CommonProblemDescriptor problem = refElementNode.getProblem();
          final RefEntity refSelected = refElementNode.getElement();
          if (problem != null) {
            showInBrowser(refSelected, problem);
          } else {
            showInBrowser(refSelected);
          }
        } else if (node instanceof ProblemDescriptionNode) {
          final ProblemDescriptionNode problemNode = (ProblemDescriptionNode) node;
          showInBrowser(problemNode.getElement(), problemNode.getDescriptor());
        } else if (node instanceof InspectionNode) {
          showInBrowser(((InspectionNode) node).getToolWrapper());
        } else {
          myBrowser.showEmpty();
        }
      }
    }
  }

  private void showInBrowser(final RefEntity refEntity) {
    Cursor currentCursor = getCursor();
    setCursor(new Cursor(Cursor.WAIT_CURSOR));
    myBrowser.showPageFor(refEntity);
    setCursor(currentCursor);
  }

  private void showInBrowser(@NotNull InspectionToolWrapper toolWrapper) {
    Cursor currentCursor = getCursor();
    setCursor(new Cursor(Cursor.WAIT_CURSOR));
    myBrowser.showDescription(toolWrapper);
    setCursor(currentCursor);
  }

  private void showInBrowser(final RefEntity refEntity, CommonProblemDescriptor descriptor) {
    Cursor currentCursor = getCursor();
    setCursor(new Cursor(Cursor.WAIT_CURSOR));
    myBrowser.showPageFor(refEntity, descriptor);
    setCursor(currentCursor);
  }

  @NotNull
  public InspectionNode addTool(
      @NotNull final InspectionToolWrapper toolWrapper,
      HighlightDisplayLevel errorLevel,
      boolean groupedBySeverity) {
    String groupName =
        toolWrapper.getGroupDisplayName().isEmpty()
            ? InspectionProfileEntry.GENERAL_GROUP_NAME
            : toolWrapper.getGroupDisplayName();
    InspectionTreeNode parentNode = getToolParentNode(groupName, errorLevel, groupedBySeverity);
    InspectionNode toolNode = new InspectionNode(toolWrapper);
    boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE;
    myProvider.appendToolNodeContent(
        myGlobalInspectionContext, toolNode, parentNode, showStructure);
    InspectionToolPresentation presentation =
        myGlobalInspectionContext.getPresentation(toolWrapper);
    toolNode =
        presentation.createToolNode(
            myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure);
    ((DefaultInspectionToolPresentation) presentation).setToolNode(toolNode);

    registerActionShortcuts(presentation);
    return toolNode;
  }

  private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) {
    final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY);
    if (fixes != null) {
      for (QuickFixAction fix : fixes) {
        fix.registerCustomShortcutSet(fix.getShortcutSet(), this);
      }
    }
  }

  private void clearTree() {
    myTree.removeAllNodes();
    mySeverityGroupNodes.clear();
  }

  @Nullable
  public String getCurrentProfileName() {
    return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName();
  }

  public InspectionProfile getCurrentProfile() {
    return myInspectionProfile;
  }

  public boolean update() {
    return updateView(true);
  }

  public boolean updateView(boolean strict) {
    if (!strict && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) {
      myTree.repaint();
      return false;
    }
    clearTree();
    boolean resultsFound = buildTree();
    myTree.restoreExpansionAndSelection();
    return resultsFound;
  }

  private boolean buildTree() {
    InspectionProfile profile = myInspectionProfile;
    boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY;
    myGroups.clear();
    final Map<String, Tools> tools = myGlobalInspectionContext.getTools();
    boolean resultsFound = false;
    for (Tools currentTools : tools.values()) {
      InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool();
      final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName());
      for (ScopeToolState state : myProvider.getTools(currentTools)) {
        InspectionToolWrapper toolWrapper = state.getTool();
        if (myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper)) {
          addTool(
              toolWrapper,
              ((InspectionProfileImpl) profile)
                  .getErrorLevel(key, state.getScope(myProject), myProject),
              isGroupedBySeverity);
          resultsFound = true;
        }
      }
    }
    return resultsFound;
  }

  @NotNull
  private InspectionTreeNode getToolParentNode(
      @NotNull String groupName, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) {
    if (groupName.isEmpty()) {
      return getRelativeRootNode(groupedBySeverity, errorLevel);
    }
    ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel);
    if (map == null) {
      map =
          ConcurrencyUtil.cacheOrGet(
              myGroups, errorLevel, ContainerUtil.<String, InspectionGroupNode>newConcurrentMap());
    }
    InspectionGroupNode group;
    if (groupedBySeverity) {
      group = map.get(groupName);
    } else {
      group = null;
      for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) {
        if ((group = groupMap.get(groupName)) != null) break;
      }
    }
    if (group == null) {
      group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName));
      addChildNodeInEDT(getRelativeRootNode(groupedBySeverity, errorLevel), group);
    }
    return group;
  }

  @NotNull
  private InspectionTreeNode getRelativeRootNode(
      boolean isGroupedBySeverity, HighlightDisplayLevel level) {
    if (isGroupedBySeverity) {
      InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level);
      if (severityGroupNode == null) {
        InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level);
        severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode);
        if (severityGroupNode == newNode) {
          InspectionTreeNode root = myTree.getRoot();
          addChildNodeInEDT(root, severityGroupNode);
        }
      }
      return severityGroupNode;
    }
    return myTree.getRoot();
  }

  private void addChildNodeInEDT(
      @NotNull final DefaultMutableTreeNode root,
      @NotNull final MutableTreeNode severityGroupNode) {
    UIUtil.invokeLaterIfNeeded(
        new Runnable() {
          @Override
          public void run() {
            if (!myDisposed) {
              root.add(severityGroupNode);
            }
          }
        });
  }

  private OccurenceNavigator getOccurenceNavigator() {
    return myOccurenceNavigator;
  }

  @Override
  public boolean hasNextOccurence() {
    return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence();
  }

  @Override
  public boolean hasPreviousOccurence() {
    return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence();
  }

  @Override
  public OccurenceInfo goNextOccurence() {
    return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null;
  }

  @Override
  public OccurenceInfo goPreviousOccurence() {
    return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null;
  }

  @Override
  public String getNextOccurenceActionName() {
    return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : "";
  }

  @Override
  public String getPreviousOccurenceActionName() {
    return myOccurenceNavigator != null
        ? myOccurenceNavigator.getPreviousOccurenceActionName()
        : "";
  }

  @NotNull
  public Project getProject() {
    return myProject;
  }

  @Override
  public Object getData(String dataId) {
    if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID;
    if (DATA_KEY.is(dataId)) return this;
    if (myTree == null) return null;
    TreePath[] paths = myTree.getSelectionPaths();

    if (paths == null || paths.length == 0) return null;

    if (paths.length > 1) {
      if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
        return collectPsiElements();
      }
      return null;
    }

    TreePath path = paths[0];

    InspectionTreeNode selectedNode = (InspectionTreeNode) path.getLastPathComponent();

    if (selectedNode instanceof RefElementNode) {
      final RefElementNode refElementNode = (RefElementNode) selectedNode;
      RefEntity refElement = refElementNode.getElement();
      if (refElement == null) return null;
      final RefEntity item = refElement.getRefManager().getRefinedElement(refElement);

      if (!item.isValid()) return null;

      PsiElement psiElement = item instanceof RefElement ? ((RefElement) item).getElement() : null;
      if (psiElement == null) return null;

      final CommonProblemDescriptor problem = refElementNode.getProblem();
      if (problem != null) {
        if (problem instanceof ProblemDescriptor) {
          psiElement = ((ProblemDescriptor) problem).getPsiElement();
          if (psiElement == null) return null;
        } else {
          return null;
        }
      }

      if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
        return getSelectedNavigatable(problem, psiElement);
      } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
        return psiElement.isValid() ? psiElement : null;
      }
    } else if (selectedNode instanceof ProblemDescriptionNode
        && CommonDataKeys.NAVIGATABLE.is(dataId)) {
      return getSelectedNavigatable(((ProblemDescriptionNode) selectedNode).getDescriptor());
    }

    return null;
  }

  @Nullable
  private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) {
    return getSelectedNavigatable(
        descriptor,
        descriptor instanceof ProblemDescriptor
            ? ((ProblemDescriptor) descriptor).getPsiElement()
            : null);
  }

  @Nullable
  private Navigatable getSelectedNavigatable(
      final CommonProblemDescriptor descriptor, final PsiElement psiElement) {
    if (descriptor instanceof ProblemDescriptorBase) {
      Navigatable navigatable = ((ProblemDescriptorBase) descriptor).getNavigatable();
      if (navigatable != null) {
        return navigatable;
      }
    }
    if (psiElement == null || !psiElement.isValid()) return null;
    PsiFile containingFile = psiElement.getContainingFile();
    VirtualFile virtualFile = containingFile == null ? null : containingFile.getVirtualFile();

    if (virtualFile != null) {
      int startOffset = psiElement.getTextOffset();
      if (descriptor instanceof ProblemDescriptorBase) {
        final TextRange textRange =
            ((ProblemDescriptorBase) descriptor).getTextRangeForNavigation();
        if (textRange != null) {
          if (virtualFile instanceof VirtualFileWindow) {
            virtualFile = ((VirtualFileWindow) virtualFile).getDelegate();
          }
          startOffset = textRange.getStartOffset();
        }
      }
      return new OpenFileDescriptor(myProject, virtualFile, startOffset);
    }
    return null;
  }

  private PsiElement[] collectPsiElements() {
    RefEntity[] refElements = myTree.getSelectedElements();
    List<PsiElement> psiElements = new ArrayList<PsiElement>();
    for (RefEntity refElement : refElements) {
      PsiElement psiElement =
          refElement instanceof RefElement ? ((RefElement) refElement).getElement() : null;
      if (psiElement != null && psiElement.isValid()) {
        psiElements.add(psiElement);
      }
    }

    return PsiUtilCore.toPsiElementArray(psiElements);
  }

  private void popupInvoked(Component component, int x, int y) {
    final TreePath path = myTree.getLeadSelectionPath();

    if (path == null) return;

    final DefaultActionGroup actions = new DefaultActionGroup();
    final ActionManager actionManager = ActionManager.getInstance();
    actions.add(actionManager.getAction(IdeActions.ACTION_EDIT_SOURCE));
    actions.add(actionManager.getAction(IdeActions.ACTION_FIND_USAGES));

    actions.add(myIncludeAction);
    actions.add(myExcludeAction);

    actions.addSeparator();

    final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper();
    if (toolWrapper != null) {
      final QuickFixAction[] quickFixes = myProvider.getQuickFixes(toolWrapper, myTree);
      if (quickFixes != null) {
        for (QuickFixAction quickFixe : quickFixes) {
          actions.add(quickFixe);
        }
      }
      final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
      if (key == null) return; // e.g. DummyEntryPointsTool

      // options
      actions.addSeparator();
      actions.add(new EditSettingsAction());
      final List<AnAction> options = new InspectionsOptionsToolbarAction(this).createActions();
      for (AnAction action : options) {
        actions.add(action);
      }
    }

    actions.addSeparator();
    actions.add(actionManager.getAction(IdeActions.GROUP_VERSION_CONTROLS));

    final ActionPopupMenu menu =
        actionManager.createActionPopupMenu(ActionPlaces.CODE_INSPECTION, actions);
    menu.getComponent().show(component, x, y);
  }

  @NotNull
  public InspectionTree getTree() {
    return myTree;
  }

  @NotNull
  public GlobalInspectionContextImpl getGlobalInspectionContext() {
    return myGlobalInspectionContext;
  }

  @NotNull
  public InspectionRVContentProvider getProvider() {
    return myProvider;
  }

  public boolean isSingleToolInSelection() {
    return myTree != null && myTree.getSelectedToolWrapper() != null;
  }

  public boolean isRerun() {
    boolean rerun = myRerun;
    myRerun = false;
    return rerun;
  }

  private InspectionProfile guessProfileToSelect(
      final InspectionProjectProfileManager profileManager) {
    final Set<InspectionProfile> profiles = new HashSet<InspectionProfile>();
    final RefEntity[] selectedElements = myTree.getSelectedElements();
    for (RefEntity selectedElement : selectedElements) {
      if (selectedElement instanceof RefElement) {
        final RefElement refElement = (RefElement) selectedElement;
        final PsiElement element = refElement.getElement();
        if (element != null) {
          profiles.add(profileManager.getInspectionProfile());
        }
      }
    }
    if (profiles.isEmpty()) {
      return (InspectionProfile) profileManager.getProjectProfileImpl();
    }
    return profiles.iterator().next();
  }

  public boolean isProfileDefined() {
    return myInspectionProfile != null && myInspectionProfile.isEditable();
  }

  public static void showPopup(AnActionEvent e, JBPopup popup) {
    final InputEvent event = e.getInputEvent();
    if (event instanceof MouseEvent) {
      popup.showUnderneathOf(event.getComponent());
    } else {
      popup.showInBestPositionFor(e.getDataContext());
    }
  }

  public AnalysisScope getScope() {
    return myScope;
  }

  private class CloseAction extends AnAction implements DumbAware {
    private CloseAction() {
      super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      myGlobalInspectionContext.close(true);
    }
  }

  private class EditSettingsAction extends AnAction {
    private EditSettingsAction() {
      super(
          InspectionsBundle.message("inspection.action.edit.settings"),
          InspectionsBundle.message("inspection.action.edit.settings"),
          AllIcons.General.Settings);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      final InspectionProjectProfileManager profileManager =
          InspectionProjectProfileManager.getInstance(myProject);
      final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper();
      InspectionProfile inspectionProfile = myInspectionProfile;
      final boolean profileIsDefined = isProfileDefined();
      if (!profileIsDefined) {
        inspectionProfile = guessProfileToSelect(profileManager);
      }

      if (toolWrapper != null) {
        final HighlightDisplayKey key =
            HighlightDisplayKey.find(
                toolWrapper.getShortName()); // do not search for dead code entry point tool
        if (key != null) {
          if (new EditInspectionToolsSettingsAction(key)
                  .editToolSettings(
                      myProject, (InspectionProfileImpl) inspectionProfile, profileIsDefined)
              && profileIsDefined) {
            updateCurrentProfile();
          }
          return;
        }
      }
      if (EditInspectionToolsSettingsAction.editToolSettings(
              myProject, inspectionProfile, profileIsDefined, null)
          && profileIsDefined) {
        updateCurrentProfile();
      }
    }
  }

  public void updateCurrentProfile() {
    final String name = myInspectionProfile.getName();
    myInspectionProfile =
        (InspectionProfile) myInspectionProfile.getProfileManager().getProfile(name);
  }

  private class RerunAction extends AnAction {
    public RerunAction(JComponent comp) {
      super(
          InspectionsBundle.message("inspection.action.rerun"),
          InspectionsBundle.message("inspection.action.rerun"),
          AllIcons.Actions.Rerun);
      registerCustomShortcutSet(CommonShortcuts.getRerun(), comp);
    }

    @Override
    public void update(AnActionEvent e) {
      e.getPresentation().setEnabled(myScope.isValid());
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      rerun();
    }

    private void rerun() {
      myRerun = true;
      if (myScope.isValid()) {
        AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions());
        myGlobalInspectionContext.doInspections(myScope);
      }
    }
  }
}
public class RefManagerImpl extends RefManager {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.codeInspection.reference.RefManager");

  private long myLastUsedMask = 256 * 256 * 256 * 8;

  @NotNull private final Project myProject;
  private AnalysisScope myScope;
  private RefProject myRefProject;
  private final Map<PsiAnchor, RefElement> myRefTable = new THashMap<>(); // guarded by myRefTable
  private List<RefElement> mySortedRefs; // guarded by myRefTable

  private final ConcurrentMap<Module, RefModule> myModules = ContainerUtil.newConcurrentMap();
  private final ProjectIterator myProjectIterator = new ProjectIterator();
  private volatile boolean myDeclarationsFound;
  private final PsiManager myPsiManager;

  private volatile boolean myIsInProcess;
  private volatile boolean myOfflineView;

  private final List<RefGraphAnnotator> myGraphAnnotators = new ArrayList<>();
  private GlobalInspectionContext myContext;

  private final Map<Key, RefManagerExtension> myExtensions = new THashMap<>();
  private final Map<Language, RefManagerExtension> myLanguageExtensions = new HashMap<>();
  private final StringInterner myNameInterner = new StringInterner();

  public RefManagerImpl(
      @NotNull Project project,
      @Nullable AnalysisScope scope,
      @NotNull GlobalInspectionContext context) {
    myProject = project;
    myScope = scope;
    myContext = context;
    myPsiManager = PsiManager.getInstance(project);
    myRefProject = new RefProjectImpl(this);
    for (InspectionExtensionsFactory factory :
        Extensions.getExtensions(InspectionExtensionsFactory.EP_NAME)) {
      final RefManagerExtension extension = factory.createRefManagerExtension(this);
      if (extension != null) {
        myExtensions.put(extension.getID(), extension);
        myLanguageExtensions.put(extension.getLanguage(), extension);
      }
    }
    if (scope != null) {
      for (Module module : ModuleManager.getInstance(getProject()).getModules()) {
        getRefModule(module);
      }
    }
  }

  public String internName(@NotNull String name) {
    synchronized (myNameInterner) {
      return myNameInterner.intern(name);
    }
  }

  @NotNull
  public GlobalInspectionContext getContext() {
    return myContext;
  }

  @Override
  public void iterate(@NotNull RefVisitor visitor) {
    for (RefElement refElement : getSortedElements()) {
      refElement.accept(visitor);
    }
    if (myModules != null) {
      for (RefModule refModule : myModules.values()) {
        refModule.accept(visitor);
      }
    }
    for (RefManagerExtension extension : myExtensions.values()) {
      extension.iterate(visitor);
    }
  }

  public void cleanup() {
    myScope = null;
    myRefProject = null;
    synchronized (myRefTable) {
      myRefTable.clear();
      mySortedRefs = null;
    }
    myModules.clear();
    myContext = null;

    myGraphAnnotators.clear();
    for (RefManagerExtension extension : myExtensions.values()) {
      extension.cleanup();
    }
  }

  @Nullable
  @Override
  public AnalysisScope getScope() {
    return myScope;
  }

  public void fireNodeInitialized(RefElement refElement) {
    for (RefGraphAnnotator annotator : myGraphAnnotators) {
      annotator.onInitialize(refElement);
    }
  }

  public void fireNodeMarkedReferenced(
      RefElement refWhat,
      RefElement refFrom,
      boolean referencedFromClassInitializer,
      final boolean forReading,
      final boolean forWriting) {
    for (RefGraphAnnotator annotator : myGraphAnnotators) {
      annotator.onMarkReferenced(
          refWhat, refFrom, referencedFromClassInitializer, forReading, forWriting);
    }
  }

  public void fireNodeMarkedReferenced(
      PsiElement what, PsiElement from, boolean referencedFromClassInitializer) {
    for (RefGraphAnnotator annotator : myGraphAnnotators) {
      annotator.onMarkReferenced(what, from, referencedFromClassInitializer);
    }
  }

  public void fireBuildReferences(RefElement refElement) {
    for (RefGraphAnnotator annotator : myGraphAnnotators) {
      annotator.onReferencesBuild(refElement);
    }
  }

  public void registerGraphAnnotator(@NotNull RefGraphAnnotator annotator) {
    myGraphAnnotators.add(annotator);
    if (annotator instanceof RefGraphAnnotatorEx) {
      ((RefGraphAnnotatorEx) annotator).initialize(this);
    }
  }

  @Override
  public long getLastUsedMask() {
    myLastUsedMask *= 2;
    return myLastUsedMask;
  }

  @Override
  public <T> T getExtension(@NotNull final Key<T> key) {
    return (T) myExtensions.get(key);
  }

  @Override
  @Nullable
  public String getType(final RefEntity ref) {
    for (RefManagerExtension extension : myExtensions.values()) {
      final String type = extension.getType(ref);
      if (type != null) return type;
    }
    if (ref instanceof RefFile) {
      return SmartRefElementPointer.FILE;
    }
    if (ref instanceof RefModule) {
      return SmartRefElementPointer.MODULE;
    }
    if (ref instanceof RefProject) {
      return SmartRefElementPointer.PROJECT;
    }
    if (ref instanceof RefDirectory) {
      return SmartRefElementPointer.DIR;
    }
    return null;
  }

  @NotNull
  @Override
  public RefEntity getRefinedElement(@NotNull RefEntity ref) {
    for (RefManagerExtension extension : myExtensions.values()) {
      ref = extension.getRefinedElement(ref);
    }
    return ref;
  }

  @Override
  public Element export(
      @NotNull RefEntity refEntity, @NotNull final Element element, final int actualLine) {
    refEntity = getRefinedElement(refEntity);

    Element problem = new Element("problem");

    if (refEntity instanceof RefElement) {
      final RefElement refElement = (RefElement) refEntity;
      final SmartPsiElementPointer pointer = refElement.getPointer();
      PsiFile psiFile = pointer.getContainingFile();
      if (psiFile == null) return null;

      Element fileElement = new Element("file");
      Element lineElement = new Element("line");
      final VirtualFile virtualFile = psiFile.getVirtualFile();
      LOG.assertTrue(virtualFile != null);
      fileElement.addContent(virtualFile.getUrl());

      if (actualLine == -1) {
        final Document document =
            PsiDocumentManager.getInstance(pointer.getProject()).getDocument(psiFile);
        LOG.assertTrue(document != null);
        final Segment range = pointer.getRange();
        lineElement.addContent(
            String.valueOf(
                range != null ? document.getLineNumber(range.getStartOffset()) + 1 : -1));
      } else {
        lineElement.addContent(String.valueOf(actualLine));
      }

      problem.addContent(fileElement);
      problem.addContent(lineElement);

      appendModule(problem, refElement.getModule());
    } else if (refEntity instanceof RefModule) {
      final RefModule refModule = (RefModule) refEntity;
      final VirtualFile moduleFile = refModule.getModule().getModuleFile();
      final Element fileElement = new Element("file");
      fileElement.addContent(moduleFile != null ? moduleFile.getUrl() : refEntity.getName());
      problem.addContent(fileElement);
      appendModule(problem, refModule);
    }

    for (RefManagerExtension extension : myExtensions.values()) {
      extension.export(refEntity, problem);
    }

    new SmartRefElementPointerImpl(refEntity, true).writeExternal(problem);
    element.addContent(problem);
    return problem;
  }

  @Override
  @Nullable
  public String getGroupName(final RefElement entity) {
    for (RefManagerExtension extension : myExtensions.values()) {
      final String groupName = extension.getGroupName(entity);
      if (groupName != null) return groupName;
    }

    final LinkedList<String> containingDirs = new LinkedList<>();
    RefEntity parent = entity.getOwner();
    while (parent != null && !(parent instanceof RefDirectory)) {
      parent = parent.getOwner();
    }
    while (parent instanceof RefDirectory) {
      containingDirs.addFirst(parent.getName());
      parent = parent.getOwner();
    }
    return containingDirs.isEmpty() ? null : StringUtil.join(containingDirs, File.separator);
  }

  private static void appendModule(final Element problem, final RefModule refModule) {
    if (refModule != null) {
      Element moduleElement = new Element("module");
      moduleElement.addContent(refModule.getName());
      problem.addContent(moduleElement);
    }
  }

  public void findAllDeclarations() {
    if (!myDeclarationsFound) {
      long before = System.currentTimeMillis();
      final AnalysisScope scope = getScope();
      if (scope != null) {
        scope.accept(myProjectIterator);
      }
      myDeclarationsFound = true;

      LOG.info(
          "Total duration of processing project usages:" + (System.currentTimeMillis() - before));
    }
  }

  public boolean isDeclarationsFound() {
    return myDeclarationsFound;
  }

  public void inspectionReadActionStarted() {
    myIsInProcess = true;
  }

  public void inspectionReadActionFinished() {
    myIsInProcess = false;
    if (myScope != null) myScope.invalidate();

    synchronized (myRefTable) {
      mySortedRefs = null;
    }
  }

  public void startOfflineView() {
    myOfflineView = true;
  }

  public boolean isOfflineView() {
    return myOfflineView;
  }

  public boolean isInProcess() {
    return myIsInProcess;
  }

  @NotNull
  @Override
  public Project getProject() {
    return myProject;
  }

  @NotNull
  @Override
  public RefProject getRefProject() {
    return myRefProject;
  }

  @NotNull
  public List<RefElement> getSortedElements() {
    List<RefElement> answer;
    synchronized (myRefTable) {
      if (mySortedRefs != null) return mySortedRefs;

      answer = new ArrayList<>(myRefTable.values());
    }
    ReadAction.run(
        () ->
            ContainerUtil.quickSort(
                answer,
                (o1, o2) -> {
                  VirtualFile v1 = ((RefElementImpl) o1).getVirtualFile();
                  VirtualFile v2 = ((RefElementImpl) o2).getVirtualFile();
                  return (v1 != null ? v1.hashCode() : 0) - (v2 != null ? v2.hashCode() : 0);
                }));
    synchronized (myRefTable) {
      return mySortedRefs = Collections.unmodifiableList(answer);
    }
  }

  @NotNull
  @Override
  public PsiManager getPsiManager() {
    return myPsiManager;
  }

  void removeReference(@NotNull RefElement refElem) {
    final PsiElement element = refElem.getElement();
    final RefManagerExtension extension =
        element != null ? getExtension(element.getLanguage()) : null;
    if (extension != null) {
      extension.removeReference(refElem);
    }

    synchronized (myRefTable) {
      mySortedRefs = null;
      if (element != null && myRefTable.remove(createAnchor(element)) != null) return;

      // PsiElement may have been invalidated and new one returned by getElement() is different so
      // we need to do this stuff.
      for (Map.Entry<PsiAnchor, RefElement> entry : myRefTable.entrySet()) {
        RefElement value = entry.getValue();
        PsiAnchor anchor = entry.getKey();
        if (value == refElem) {
          myRefTable.remove(anchor);
          break;
        }
      }
    }
  }

  @NotNull
  private static PsiAnchor createAnchor(@NotNull final PsiElement element) {
    return ApplicationManager.getApplication()
        .runReadAction(
            new Computable<PsiAnchor>() {
              @Override
              public PsiAnchor compute() {
                return PsiAnchor.create(element);
              }
            });
  }

  public void initializeAnnotators() {
    ExtensionPoint<RefGraphAnnotator> point =
        Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.INSPECTIONS_GRAPH_ANNOTATOR);
    final RefGraphAnnotator[] graphAnnotators = point.getExtensions();
    for (RefGraphAnnotator annotator : graphAnnotators) {
      registerGraphAnnotator(annotator);
    }
  }

  private class ProjectIterator extends PsiElementVisitor {
    @Override
    public void visitElement(PsiElement element) {
      final RefManagerExtension extension = getExtension(element.getLanguage());
      if (extension != null) {
        extension.visitElement(element);
      }
      for (PsiElement aChildren : element.getChildren()) {
        aChildren.accept(this);
      }
    }

    @Override
    public void visitFile(PsiFile file) {
      final VirtualFile virtualFile = file.getVirtualFile();
      if (virtualFile != null) {
        String relative =
            ProjectUtilCore.displayUrlRelativeToProject(
                virtualFile, virtualFile.getPresentableUrl(), myProject, true, false);
        myContext.incrementJobDoneAmount(myContext.getStdJobDescriptors().BUILD_GRAPH, relative);
      }
      final FileViewProvider viewProvider = file.getViewProvider();
      final Set<Language> relevantLanguages = viewProvider.getLanguages();
      for (Language language : relevantLanguages) {
        visitElement(viewProvider.getPsi(language));
      }
      myPsiManager.dropResolveCaches();
      InjectedLanguageManager.getInstance(myProject).dropFileCaches(file);
    }
  }

  @Override
  @Nullable
  public RefElement getReference(final PsiElement elem) {
    return getReference(elem, false);
  }

  @Nullable
  public RefElement getReference(final PsiElement elem, final boolean ignoreScope) {
    if (ApplicationManager.getApplication()
        .runReadAction(
            new Computable<Boolean>() {
              @Override
              public Boolean compute() {
                return elem == null
                    || !elem.isValid()
                    || elem instanceof LightElement
                    || !(elem instanceof PsiDirectory) && !belongsToScope(elem, ignoreScope);
              }
            })) {
      return null;
    }

    return getFromRefTableOrCache(
        elem,
        () ->
            ApplicationManager.getApplication()
                .runReadAction(
                    new Computable<RefElementImpl>() {
                      @Override
                      @Nullable
                      public RefElementImpl compute() {
                        final RefManagerExtension extension = getExtension(elem.getLanguage());
                        if (extension != null) {
                          final RefElement refElement = extension.createRefElement(elem);
                          if (refElement != null) return (RefElementImpl) refElement;
                        }
                        if (elem instanceof PsiFile) {
                          return new RefFileImpl((PsiFile) elem, RefManagerImpl.this);
                        }
                        if (elem instanceof PsiDirectory) {
                          return new RefDirectoryImpl((PsiDirectory) elem, RefManagerImpl.this);
                        }
                        return null;
                      }
                    }),
        element -> {
          element.initialize();
          for (RefManagerExtension each : myExtensions.values()) {
            each.onEntityInitialized(element, elem);
          }
          fireNodeInitialized(element);
        });
  }

  private RefManagerExtension getExtension(final Language language) {
    return myLanguageExtensions.get(language);
  }

  @Nullable
  @Override
  public RefEntity getReference(final String type, final String fqName) {
    for (RefManagerExtension extension : myExtensions.values()) {
      final RefEntity refEntity = extension.getReference(type, fqName);
      if (refEntity != null) return refEntity;
    }
    if (SmartRefElementPointer.FILE.equals(type)) {
      return RefFileImpl.fileFromExternalName(this, fqName);
    }
    if (SmartRefElementPointer.MODULE.equals(type)) {
      return RefModuleImpl.moduleFromName(this, fqName);
    }
    if (SmartRefElementPointer.PROJECT.equals(type)) {
      return getRefProject();
    }
    if (SmartRefElementPointer.DIR.equals(type)) {
      String url =
          VfsUtilCore.pathToUrl(PathMacroManager.getInstance(getProject()).expandPath(fqName));
      VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(url);
      if (vFile != null) {
        final PsiDirectory dir = PsiManager.getInstance(getProject()).findDirectory(vFile);
        return getReference(dir);
      }
    }
    return null;
  }

  @Nullable
  protected <T extends RefElement> T getFromRefTableOrCache(
      final PsiElement element, @NotNull NullableFactory<T> factory) {
    return getFromRefTableOrCache(element, factory, null);
  }

  @Nullable
  private <T extends RefElement> T getFromRefTableOrCache(
      final PsiElement element,
      @NotNull NullableFactory<T> factory,
      @Nullable Consumer<T> whenCached) {

    PsiAnchor psiAnchor = createAnchor(element);
    T result;
    synchronized (myRefTable) {
      //noinspection unchecked
      result = (T) myRefTable.get(psiAnchor);

      if (result != null) return result;

      if (!isValidPointForReference()) {
        // LOG.assertTrue(true, "References may become invalid after process is finished");
        return null;
      }

      result = factory.create();
      if (result == null) return null;

      myRefTable.put(psiAnchor, result);
      mySortedRefs = null;

      if (whenCached != null) {
        whenCached.consume(result);
      }
    }

    return result;
  }

  @Override
  public RefModule getRefModule(Module module) {
    if (module == null) {
      return null;
    }
    RefModule refModule = myModules.get(module);
    if (refModule == null) {
      refModule = ConcurrencyUtil.cacheOrGet(myModules, module, new RefModuleImpl(module, this));
    }
    return refModule;
  }

  @Override
  public boolean belongsToScope(final PsiElement psiElement) {
    return belongsToScope(psiElement, false);
  }

  private boolean belongsToScope(final PsiElement psiElement, final boolean ignoreScope) {
    if (psiElement == null || !psiElement.isValid()) return false;
    if (psiElement instanceof PsiCompiledElement) return false;
    final PsiFile containingFile =
        ApplicationManager.getApplication()
            .runReadAction(
                new Computable<PsiFile>() {
                  @Override
                  public PsiFile compute() {
                    return psiElement.getContainingFile();
                  }
                });
    if (containingFile == null) {
      return false;
    }
    for (RefManagerExtension extension : myExtensions.values()) {
      if (!extension.belongsToScope(psiElement)) return false;
    }
    final Boolean inProject =
        ApplicationManager.getApplication()
            .runReadAction(
                new Computable<Boolean>() {
                  @Override
                  public Boolean compute() {
                    return psiElement.getManager().isInProject(psiElement);
                  }
                });
    return inProject.booleanValue()
        && (ignoreScope || getScope() == null || getScope().contains(psiElement));
  }

  @Override
  public String getQualifiedName(RefEntity refEntity) {
    if (refEntity == null || refEntity instanceof RefElementImpl && !refEntity.isValid()) {
      return InspectionsBundle.message("inspection.reference.invalid");
    }

    return refEntity.getQualifiedName();
  }

  @Override
  public void removeRefElement(
      @NotNull RefElement refElement, @NotNull List<RefElement> deletedRefs) {
    List<RefEntity> children = refElement.getChildren();
    if (children != null) {
      RefElement[] refElements = children.toArray(new RefElement[children.size()]);
      for (RefElement refChild : refElements) {
        removeRefElement(refChild, deletedRefs);
      }
    }

    ((RefManagerImpl) refElement.getRefManager()).removeReference(refElement);
    ((RefElementImpl) refElement).referenceRemoved();
    if (!deletedRefs.contains(refElement)) deletedRefs.add(refElement);
  }

  protected boolean isValidPointForReference() {
    return myIsInProcess || myOfflineView || ApplicationManager.getApplication().isUnitTestMode();
  }
}
public abstract class PsiDocumentManagerBase extends PsiDocumentManager
    implements DocumentListener {
  static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl");
  private static final Key<Document> HARD_REF_TO_DOCUMENT =
      Key.create("HARD_REFERENCE_TO_DOCUMENT");
  private static final Key<PsiFile> HARD_REF_TO_PSI = Key.create("HARD_REFERENCE_TO_PSI");
  private static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT");

  protected final Project myProject;
  private final PsiManager myPsiManager;
  private final DocumentCommitProcessor myDocumentCommitProcessor;
  protected final Set<Document> myUncommittedDocuments = ContainerUtil.newConcurrentSet();
  private final Map<Document, Pair<CharSequence, Long>> myLastCommittedTexts =
      ContainerUtil.newConcurrentMap();
  protected boolean myStopTrackingDocuments;
  protected boolean myPerformBackgroundCommit = true;

  private volatile boolean myIsCommitInProgress;
  private final PsiToDocumentSynchronizer mySynchronizer;

  private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();

  protected PsiDocumentManagerBase(
      @NotNull final Project project,
      @NotNull PsiManager psiManager,
      @NotNull MessageBus bus,
      @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) {
    myProject = project;
    myPsiManager = psiManager;
    myDocumentCommitProcessor = documentCommitProcessor;
    mySynchronizer = new PsiToDocumentSynchronizer(this, bus);
    myPsiManager.addPsiTreeChangeListener(mySynchronizer);
    bus.connect()
        .subscribe(
            PsiDocumentTransactionListener.TOPIC,
            new PsiDocumentTransactionListener() {
              @Override
              public void transactionStarted(@NotNull Document document, @NotNull PsiFile file) {
                myUncommittedDocuments.remove(document);
              }

              @Override
              public void transactionCompleted(@NotNull Document document, @NotNull PsiFile file) {}
            });
  }

  @Override
  @Nullable
  public PsiFile getPsiFile(@NotNull Document document) {
    final PsiFile userData = document.getUserData(HARD_REF_TO_PSI);
    if (userData != null) return userData;

    PsiFile psiFile = getCachedPsiFile(document);
    if (psiFile != null) return psiFile;

    final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !virtualFile.isValid()) return null;

    psiFile = getPsiFile(virtualFile);
    if (psiFile == null) return null;

    fireFileCreated(document, psiFile);

    return psiFile;
  }

  public static void cachePsi(@NotNull Document document, @Nullable PsiFile file) {
    document.putUserData(HARD_REF_TO_PSI, file);
  }

  @Override
  public PsiFile getCachedPsiFile(@NotNull Document document) {
    final PsiFile userData = document.getUserData(HARD_REF_TO_PSI);
    if (userData != null) return userData;

    final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !virtualFile.isValid()) return null;
    return getCachedPsiFile(virtualFile);
  }

  @Nullable
  FileViewProvider getCachedViewProvider(@NotNull Document document) {
    final VirtualFile virtualFile = getVirtualFile(document);
    if (virtualFile == null) return null;
    return getCachedViewProvider(virtualFile);
  }

  private FileViewProvider getCachedViewProvider(@NotNull VirtualFile virtualFile) {
    return ((PsiManagerEx) myPsiManager).getFileManager().findCachedViewProvider(virtualFile);
  }

  private static VirtualFile getVirtualFile(@NotNull Document document) {
    final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !virtualFile.isValid()) return null;
    return virtualFile;
  }

  @Nullable
  PsiFile getCachedPsiFile(@NotNull VirtualFile virtualFile) {
    return ((PsiManagerEx) myPsiManager).getFileManager().getCachedPsiFile(virtualFile);
  }

  @Nullable
  private PsiFile getPsiFile(@NotNull VirtualFile virtualFile) {
    return ((PsiManagerEx) myPsiManager).getFileManager().findFile(virtualFile);
  }

  @Nullable
  @Override
  public Document getDocument(@NotNull PsiFile file) {
    if (file instanceof PsiBinaryFile) return null;

    Document document = getCachedDocument(file);
    if (document != null) {
      if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) {
        PsiUtilCore.ensureValid(file);
        cachePsi(document, file);
      }
      return document;
    }

    FileViewProvider viewProvider = file.getViewProvider();
    if (!viewProvider.isEventSystemEnabled()) return null;

    document = FileDocumentManager.getInstance().getDocument(viewProvider.getVirtualFile());
    if (document != null) {
      if (document.getTextLength() != file.getTextLength()) {
        String message =
            "Document/PSI mismatch: "
                + file
                + " ("
                + file.getClass()
                + "); physical="
                + viewProvider.isPhysical();
        if (document.getTextLength() + file.getTextLength() < 8096) {
          message +=
              "\n=== document ===\n" + document.getText() + "\n=== PSI ===\n" + file.getText();
        }
        throw new AssertionError(message);
      }

      if (!viewProvider.isPhysical()) {
        PsiUtilCore.ensureValid(file);
        cachePsi(document, file);
        file.putUserData(HARD_REF_TO_DOCUMENT, document);
      }
    }

    return document;
  }

  @Override
  public Document getCachedDocument(@NotNull PsiFile file) {
    if (!file.isPhysical()) return null;
    VirtualFile vFile = file.getViewProvider().getVirtualFile();
    return FileDocumentManager.getInstance().getCachedDocument(vFile);
  }

  @Override
  public void commitAllDocuments() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (myUncommittedDocuments.isEmpty()) return;

    final Document[] documents = getUncommittedDocuments();
    for (Document document : documents) {
      commitDocument(document);
    }

    LOG.assertTrue(!hasUncommitedDocuments(), myUncommittedDocuments);
  }

  @Override
  public void performForCommittedDocument(
      @NotNull final Document doc, @NotNull final Runnable action) {
    final Document document =
        doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc;
    if (isCommitted(document)) {
      action.run();
    } else {
      addRunOnCommit(document, action);
    }
  }

  private final Map<Object, Runnable> actionsWhenAllDocumentsAreCommitted =
      new LinkedHashMap<Object, Runnable>(); // accessed from EDT only
  private static final Object PERFORM_ALWAYS_KEY =
      new Object() {
        @Override
        @NonNls
        public String toString() {
          return "PERFORM_ALWAYS";
        }
      };

  /**
   * Cancel previously registered action and schedules (new) action to be executed when all
   * documents are committed.
   *
   * @param key the (unique) id of the action.
   * @param action The action to be executed after automatic commit. This action will overwrite any
   *     action which was registered under this key earlier. The action will be executed in EDT.
   * @return true if action has been run immediately, or false if action was scheduled for execution
   *     later.
   */
  public boolean cancelAndRunWhenAllCommitted(
      @NonNls @NotNull Object key, @NotNull final Runnable action) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    if (myProject.isDisposed()) {
      action.run();
      return true;
    }
    if (myUncommittedDocuments.isEmpty()) {
      action.run();
      if (!hasUncommitedDocuments()) {
        assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted;
      }
      return true;
    }
    actionsWhenAllDocumentsAreCommitted.put(key, action);
    return false;
  }

  public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) {
    synchronized (ACTION_AFTER_COMMIT) {
      List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT);
      if (list == null) {
        document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>());
      }
      list.add(action);
    }
  }

  @Override
  public void commitDocument(@NotNull final Document doc) {
    final Document document =
        doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc;
    if (!isCommitted(document)) {
      doCommit(document);
    }
  }

  // public for Upsource
  public boolean finishCommit(
      @NotNull final Document document,
      @NotNull final List<Processor<Document>> finishProcessors,
      final boolean synchronously,
      @NotNull final Object reason) {
    assert !myProject.isDisposed() : "Already disposed";
    final boolean[] ok = {true};
    ApplicationManager.getApplication()
        .runWriteAction(
            new CommitToPsiFileAction(document, myProject) {
              @Override
              public void run() {
                ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously);
              }
            });

    if (ok[0]) {
      // otherwise changes maybe not synced to the document yet, and injectors will crash
      if (!mySynchronizer.isDocumentAffectedByTransactions(document)) {
        InjectedLanguageManager.getInstance(myProject).startRunInjectors(document, synchronously);
      }
      // run after commit actions outside write action
      runAfterCommitActions(document);
      if (DebugUtil.DO_EXPENSIVE_CHECKS && !ApplicationInfoImpl.isInPerformanceTest()) {
        checkAllElementsValid(document, reason);
      }
    }
    return ok[0];
  }

  protected boolean finishCommitInWriteAction(
      @NotNull final Document document,
      @NotNull final List<Processor<Document>> finishProcessors,
      final boolean synchronously) {
    if (myProject.isDisposed()) return false;
    assert !(document instanceof DocumentWindow);
    myIsCommitInProgress = true;
    boolean success = true;
    try {
      final FileViewProvider viewProvider = getCachedViewProvider(document);
      if (viewProvider != null) {
        for (Processor<Document> finishRunnable : finishProcessors) {
          success = finishRunnable.process(document);
          if (synchronously) {
            assert success : finishRunnable + " in " + finishProcessors;
          }
          if (!success) {
            break;
          }
        }
        if (success) {
          myLastCommittedTexts.remove(document);
          viewProvider.contentsSynchronized();
        }
      } else {
        handleCommitWithoutPsi(document);
      }
    } finally {
      myDocumentCommitProcessor.log(
          "in PDI.finishDoc: ", null, synchronously, success, myUncommittedDocuments);
      if (success) {
        myUncommittedDocuments.remove(document);
        myDocumentCommitProcessor.log(
            "in PDI.finishDoc: removed doc", null, synchronously, success, myUncommittedDocuments);
      }
      myIsCommitInProgress = false;
      myDocumentCommitProcessor.log(
          "in PDI.finishDoc: exit", null, synchronously, success, myUncommittedDocuments);
    }

    return success;
  }

  private void checkAllElementsValid(@NotNull Document document, @NotNull final Object reason) {
    final PsiFile psiFile = getCachedPsiFile(document);
    if (psiFile != null) {
      psiFile.accept(
          new PsiRecursiveElementWalkingVisitor() {
            @Override
            public void visitElement(PsiElement element) {
              if (!element.isValid()) {
                throw new AssertionError(
                    "Commit to '"
                        + psiFile.getVirtualFile()
                        + "' has led to invalid element: "
                        + element
                        + "; Reason: '"
                        + reason
                        + "'");
              }
            }
          });
    }
  }

  private void doCommit(@NotNull final Document document) {
    assert !myIsCommitInProgress : "Do not call commitDocument() from inside PSI change listener";
    ApplicationManager.getApplication()
        .runWriteAction(
            new Runnable() {
              @Override
              public void run() {
                // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged()
                if (getSynchronizer().isDocumentAffectedByTransactions(document)) return;

                myIsCommitInProgress = true;
                try {
                  myDocumentCommitProcessor.commitSynchronously(document, myProject);
                } finally {
                  myIsCommitInProgress = false;
                }
                assert !isInUncommittedSet(document) : "Document :" + document;
              }
            });
  }

  @Override
  public <T> T commitAndRunReadAction(@NotNull final Computable<T> computation) {
    final Ref<T> ref = Ref.create(null);
    commitAndRunReadAction(
        new Runnable() {
          @Override
          public void run() {
            ref.set(computation.compute());
          }
        });
    return ref.get();
  }

  @Override
  public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) {
    FileContentUtilCore.reparseFiles(files);
  }

  @Override
  public void commitAndRunReadAction(@NotNull final Runnable runnable) {
    final Application application = ApplicationManager.getApplication();
    if (SwingUtilities.isEventDispatchThread()) {
      commitAllDocuments();
      runnable.run();
    } else {
      if (ApplicationManager.getApplication().isReadAccessAllowed()) {
        LOG.error(
            "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise. "
                + Thread.currentThread());
      }

      final Semaphore s1 = new Semaphore();
      final Semaphore s2 = new Semaphore();
      final boolean[] committed = {false};

      application.runReadAction(
          new Runnable() {
            @Override
            public void run() {
              if (myUncommittedDocuments.isEmpty()) {
                runnable.run();
                committed[0] = true;
              } else {
                s1.down();
                s2.down();
                final Runnable commitRunnable =
                    new Runnable() {
                      @Override
                      public void run() {
                        commitAllDocuments();
                        s1.up();
                        s2.waitFor();
                      }
                    };
                final ProgressIndicator progressIndicator =
                    ProgressManager.getInstance().getProgressIndicator();
                if (progressIndicator == null) {
                  ApplicationManager.getApplication().invokeLater(commitRunnable);
                } else {
                  ApplicationManager.getApplication()
                      .invokeLater(commitRunnable, progressIndicator.getModalityState());
                }
              }
            }
          });

      if (!committed[0]) {
        s1.waitFor();
        application.runReadAction(
            new Runnable() {
              @Override
              public void run() {
                s2.up();
                runnable.run();
              }
            });
      }
    }
  }

  /**
   * Schedules action to be executed when all documents are committed.
   *
   * @return true if action has been run immediately, or false if action was scheduled for execution
   *     later.
   */
  @Override
  public boolean performWhenAllCommitted(@NotNull final Runnable action) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    assert !myProject.isDisposed() : "Already disposed: " + myProject;
    if (myUncommittedDocuments.isEmpty()) {
      action.run();
      return true;
    }
    CompositeRunnable actions =
        (CompositeRunnable) actionsWhenAllDocumentsAreCommitted.get(PERFORM_ALWAYS_KEY);
    if (actions == null) {
      actions = new CompositeRunnable();
      actionsWhenAllDocumentsAreCommitted.put(PERFORM_ALWAYS_KEY, actions);
    }
    actions.add(action);
    myDocumentCommitProcessor.log(
        "PDI: added performWhenAllCommitted", null, false, action, myUncommittedDocuments);
    return false;
  }

  private static class CompositeRunnable extends ArrayList<Runnable> implements Runnable {
    @Override
    public void run() {
      for (Runnable runnable : this) {
        runnable.run();
      }
    }
  }

  private void runAfterCommitActions(@NotNull Document document) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    List<Runnable> list;
    synchronized (ACTION_AFTER_COMMIT) {
      list = document.getUserData(ACTION_AFTER_COMMIT);
      if (list != null) {
        list = new ArrayList<Runnable>(list);
        document.putUserData(ACTION_AFTER_COMMIT, null);
      }
    }
    if (list != null) {
      for (final Runnable runnable : list) {
        runnable.run();
      }
    }

    if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) {
      List<Object> keys = new ArrayList<Object>(actionsWhenAllDocumentsAreCommitted.keySet());
      for (Object key : keys) {
        try {
          Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key);
          myDocumentCommitProcessor.log(
              "Running after commit runnable: ", null, false, key, action);
          action.run();
        } catch (Throwable e) {
          LOG.error(e);
        }
      }
    }
  }

  @Override
  public void addListener(@NotNull Listener listener) {
    myListeners.add(listener);
  }

  @Override
  public void removeListener(@NotNull Listener listener) {
    myListeners.remove(listener);
  }

  @Override
  public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
    return false;
  }

  @Override
  public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {}

  void fireDocumentCreated(@NotNull Document document, PsiFile file) {
    for (Listener listener : myListeners) {
      listener.documentCreated(document, file);
    }
  }

  private void fireFileCreated(@NotNull Document document, @NotNull PsiFile file) {
    for (Listener listener : myListeners) {
      listener.fileCreated(file, document);
    }
  }

  @Override
  @NotNull
  public CharSequence getLastCommittedText(@NotNull Document document) {
    Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document);
    return pair != null ? pair.first : document.getImmutableCharSequence();
  }

  @Override
  public long getLastCommittedStamp(@NotNull Document document) {
    Pair<CharSequence, Long> pair = myLastCommittedTexts.get(document);
    return pair != null ? pair.second : document.getModificationStamp();
  }

  @Override
  @NotNull
  public Document[] getUncommittedDocuments() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    Document[] documents =
        myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]);
    return ArrayUtil.stripTrailingNulls(documents);
  }

  boolean isInUncommittedSet(@NotNull Document document) {
    if (document instanceof DocumentWindow)
      return isInUncommittedSet(((DocumentWindow) document).getDelegate());
    return myUncommittedDocuments.contains(document);
  }

  @Override
  public boolean isUncommited(@NotNull Document document) {
    return !isCommitted(document);
  }

  @Override
  public boolean isCommitted(@NotNull Document document) {
    if (document instanceof DocumentWindow)
      return isCommitted(((DocumentWindow) document).getDelegate());
    if (getSynchronizer().isInSynchronization(document)) return true;
    return !((DocumentEx) document).isInEventsHandling() && !isInUncommittedSet(document);
  }

  @Override
  public boolean hasUncommitedDocuments() {
    return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty();
  }

  @Override
  public void beforeDocumentChange(@NotNull DocumentEvent event) {
    if (myStopTrackingDocuments) return;

    final Document document = event.getDocument();
    if (!(document instanceof DocumentWindow) && !myLastCommittedTexts.containsKey(document)) {
      myLastCommittedTexts.put(
          document,
          Pair.create(document.getImmutableCharSequence(), document.getModificationStamp()));
    }

    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    boolean isRelevant = virtualFile != null && isRelevant(virtualFile);

    final FileViewProvider viewProvider = getCachedViewProvider(document);
    boolean inMyProject = viewProvider != null && viewProvider.getManager() == myPsiManager;
    if (!isRelevant || !inMyProject) {
      return;
    }

    final List<PsiFile> files = viewProvider.getAllFiles();
    PsiFile psiCause = null;
    for (PsiFile file : files) {
      if (file == null) {
        throw new AssertionError(
            "View provider "
                + viewProvider
                + " ("
                + viewProvider.getClass()
                + ") returned null in its files array: "
                + files
                + " for file "
                + viewProvider.getVirtualFile());
      }

      if (mySynchronizer.isInsideAtomicChange(file)) {
        psiCause = file;
      }
    }

    if (psiCause == null) {
      beforeDocumentChangeOnUnlockedDocument(viewProvider);
    }

    ((SingleRootFileViewProvider) viewProvider).beforeDocumentChanged(psiCause);
  }

  protected void beforeDocumentChangeOnUnlockedDocument(
      @NotNull final FileViewProvider viewProvider) {}

  @Override
  public void documentChanged(DocumentEvent event) {
    if (myStopTrackingDocuments) return;

    final Document document = event.getDocument();
    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    boolean isRelevant = virtualFile != null && isRelevant(virtualFile);

    final FileViewProvider viewProvider = getCachedViewProvider(document);
    if (viewProvider == null) {
      handleCommitWithoutPsi(document);
      return;
    }
    boolean inMyProject = viewProvider.getManager() == myPsiManager;
    if (!isRelevant || !inMyProject) {
      myLastCommittedTexts.remove(document);
      return;
    }

    ApplicationManager.getApplication().assertWriteAccessAllowed();
    final List<PsiFile> files = viewProvider.getAllFiles();
    boolean commitNecessary = true;
    for (PsiFile file : files) {

      if (mySynchronizer.isInsideAtomicChange(file)) {
        commitNecessary = false;
        continue;
      }

      assert file instanceof PsiFileImpl
              || "mock.file".equals(file.getName())
                  && ApplicationManager.getApplication().isUnitTestMode()
          : event + "; file=" + file + "; allFiles=" + files + "; viewProvider=" + viewProvider;
    }

    boolean forceCommit =
        ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class)
            && (SystemProperties.getBooleanProperty("idea.force.commit.on.external.change", false)
                || ApplicationManager.getApplication().isHeadlessEnvironment()
                    && !ApplicationManager.getApplication().isUnitTestMode());

    // Consider that it's worth to perform complete re-parse instead of merge if the whole document
    // text is replaced and
    // current document lines number is roughly above 5000. This makes sense in situations when
    // external change is performed
    // for the huge file (that causes the whole document to be reloaded and 'merge' way takes a
    // while to complete).
    if (event.isWholeTextReplaced() && document.getTextLength() > 100000) {
      document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE);
    }

    if (commitNecessary) {
      assert !(document instanceof DocumentWindow);
      myUncommittedDocuments.add(document);
      myDocumentCommitProcessor.log(
          "added uncommitted doc",
          null,
          false,
          myProject,
          document,
          ((DocumentEx) document).isInBulkUpdate());
      if (forceCommit) {
        commitDocument(document);
      } else if (!((DocumentEx) document).isInBulkUpdate() && myPerformBackgroundCommit) {
        myDocumentCommitProcessor.commitAsynchronously(myProject, document, event);
      }
    } else {
      myLastCommittedTexts.remove(document);
    }
  }

  void handleCommitWithoutPsi(@NotNull Document document) {
    final Pair<CharSequence, Long> prevPair = myLastCommittedTexts.remove(document);
    if (prevPair == null) {
      return;
    }

    if (!myProject.isInitialized() || myProject.isDisposed()) {
      return;
    }

    myUncommittedDocuments.remove(document);

    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
    if (virtualFile == null || !FileIndexFacade.getInstance(myProject).isInContent(virtualFile)) {
      return;
    }

    final PsiFile psiFile = getPsiFile(document);
    if (psiFile == null) {
      return;
    }

    // we can end up outside write action here if the document has forUseInNonAWTThread=true
    ApplicationManager.getApplication()
        .runWriteAction(
            new ExternalChangeAction() {
              @Override
              public void run() {
                psiFile.getViewProvider().beforeContentsSynchronized();
                synchronized (PsiLock.LOCK) {
                  final int oldLength = prevPair.first.length();
                  PsiManagerImpl manager = (PsiManagerImpl) psiFile.getManager();
                  BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true);
                  BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false);
                  if (psiFile instanceof PsiFileImpl) {
                    ((PsiFileImpl) psiFile).onContentReload();
                  }
                  BlockSupportImpl.sendAfterChildrenChangedEvent(
                      manager, psiFile, oldLength, false);
                  BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true);
                }
                psiFile.getViewProvider().contentsSynchronized();
              }
            });
  }

  private boolean isRelevant(@NotNull VirtualFile virtualFile) {
    return !virtualFile.getFileType().isBinary() && !myProject.isDisposed();
  }

  public static boolean checkConsistency(@NotNull PsiFile psiFile, @NotNull Document document) {
    // todo hack
    if (psiFile.getVirtualFile() == null) return true;

    CharSequence editorText = document.getCharsSequence();
    int documentLength = document.getTextLength();
    if (psiFile.textMatches(editorText)) {
      LOG.assertTrue(psiFile.getTextLength() == documentLength);
      return true;
    }

    char[] fileText = psiFile.textToCharArray();
    @SuppressWarnings("NonConstantStringShouldBeStringBuffer")
    @NonNls
    String error =
        "File '"
            + psiFile.getName()
            + "' text mismatch after reparse. "
            + "File length="
            + fileText.length
            + "; Doc length="
            + documentLength
            + "\n";
    int i = 0;
    for (; i < documentLength; i++) {
      if (i >= fileText.length) {
        error += "editorText.length > psiText.length i=" + i + "\n";
        break;
      }
      if (i >= editorText.length()) {
        error += "editorText.length > psiText.length i=" + i + "\n";
        break;
      }
      if (editorText.charAt(i) != fileText[i]) {
        error += "first unequal char i=" + i + "\n";
        break;
      }
    }
    // error += "*********************************************" + "\n";
    // if (i <= 500){
    //  error += "Equal part:" + editorText.subSequence(0, i) + "\n";
    // }
    // else{
    //  error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n";
    //  error += "................................................" + "\n";
    //  error += "................................................" + "\n";
    //  error += "................................................" + "\n";
    //  error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n";
    // }
    error += "*********************************************" + "\n";
    error +=
        "Editor Text tail:("
            + (documentLength - i)
            + ")\n"; // + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n";
    error += "*********************************************" + "\n";
    error += "Psi Text tail:(" + (fileText.length - i) + ")\n";
    error += "*********************************************" + "\n";

    if (document instanceof DocumentWindow) {
      error += "doc: '" + document.getText() + "'\n";
      error += "psi: '" + psiFile.getText() + "'\n";
      error += "ast: '" + psiFile.getNode().getText() + "'\n";
      error += psiFile.getLanguage() + "\n";
      PsiElement context =
          InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile);
      if (context != null) {
        error += "context: " + context + "; text: '" + context.getText() + "'\n";
        error += "context file: " + context.getContainingFile() + "\n";
      }
      error +=
          "document window ranges: "
              + Arrays.asList(((DocumentWindow) document).getHostRanges())
              + "\n";
    }
    LOG.error(error);
    // document.replaceString(0, documentLength, psiFile.getText());
    return false;
  }

  @TestOnly
  public void clearUncommittedDocuments() {
    myLastCommittedTexts.clear();
    myUncommittedDocuments.clear();
    mySynchronizer.cleanupForNextTest();
  }

  @TestOnly
  public void disableBackgroundCommit(@NotNull Disposable parentDisposable) {
    assert myPerformBackgroundCommit;
    myPerformBackgroundCommit = false;
    Disposer.register(
        parentDisposable,
        new Disposable() {
          @Override
          public void dispose() {
            myPerformBackgroundCommit = true;
          }
        });
  }

  @NotNull
  public PsiToDocumentSynchronizer getSynchronizer() {
    return mySynchronizer;
  }
}
Example #17
0
public final class IconLoader {
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.IconLoader");
  public static boolean STRICT = false;
  private static boolean USE_DARK_ICONS = UIUtil.isUnderDarcula();
  private static float SCALE = JBUI.scale(1f);
  private static ImageFilter IMAGE_FILTER;

  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
  private static final ConcurrentMap<URL, CachedImageIcon> ourIconsCache =
      ContainerUtil.newConcurrentMap(100, 0.9f, 2);

  /** This cache contains mapping between icons and disabled icons. */
  private static final Map<Icon, Icon> ourIcon2DisabledIcon = new WeakHashMap<Icon, Icon>(200);

  @NonNls
  private static final Map<String, String> ourDeprecatedIconsReplacements =
      new HashMap<String, String>();

  static {
    ourDeprecatedIconsReplacements.put(
        "/general/toolWindowDebugger.png", "AllIcons.Toolwindows.ToolWindowDebugger");
    ourDeprecatedIconsReplacements.put(
        "/general/toolWindowChanges.png", "AllIcons.Toolwindows.ToolWindowChanges");

    ourDeprecatedIconsReplacements.put(
        "/actions/showSettings.png", "AllIcons.General.ProjectSettings");
    ourDeprecatedIconsReplacements.put("/general/ideOptions.png", "AllIcons.General.Settings");
    ourDeprecatedIconsReplacements.put(
        "/general/applicationSettings.png", "AllIcons.General.Settings");
    ourDeprecatedIconsReplacements.put("/toolbarDecorator/add.png", "AllIcons.General.Add");
    ourDeprecatedIconsReplacements.put("/vcs/customizeView.png", "AllIcons.General.Settings");

    ourDeprecatedIconsReplacements.put("/vcs/refresh.png", "AllIcons.Actions.Refresh");
    ourDeprecatedIconsReplacements.put("/actions/sync.png", "AllIcons.Actions.Refresh");
    ourDeprecatedIconsReplacements.put("/actions/refreshUsages.png", "AllIcons.Actions.Rerun");

    ourDeprecatedIconsReplacements.put("/compiler/error.png", "AllIcons.General.Error");
    ourDeprecatedIconsReplacements.put(
        "/compiler/hideWarnings.png", "AllIcons.General.HideWarnings");
    ourDeprecatedIconsReplacements.put("/compiler/information.png", "AllIcons.General.Information");
    ourDeprecatedIconsReplacements.put("/compiler/warning.png", "AllIcons.General.Warning");
    ourDeprecatedIconsReplacements.put("/ide/errorSign.png", "AllIcons.General.Error");

    ourDeprecatedIconsReplacements.put("/ant/filter.png", "AllIcons.General.Filter");
    ourDeprecatedIconsReplacements.put("/inspector/useFilter.png", "AllIcons.General.Filter");

    ourDeprecatedIconsReplacements.put("/actions/showSource.png", "AllIcons.Actions.Preview");
    ourDeprecatedIconsReplacements.put(
        "/actions/consoleHistory.png", "AllIcons.General.MessageHistory");
    ourDeprecatedIconsReplacements.put(
        "/vcs/messageHistory.png", "AllIcons.General.MessageHistory");
  }

  private static final ImageIcon EMPTY_ICON =
      new ImageIcon(UIUtil.createImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)) {
        @NonNls
        public String toString() {
          return "Empty icon " + super.toString();
        }
      };

  private static boolean ourIsActivated = false;

  private IconLoader() {}

  @Deprecated
  public static Icon getIcon(@NotNull final Image image) {
    return new JBImageIcon(image);
  }

  public static void setUseDarkIcons(boolean useDarkIcons) {
    USE_DARK_ICONS = useDarkIcons;
    clearCache();
  }

  public static void setScale(float scale) {
    if (scale != SCALE) {
      SCALE = scale;
      clearCache();
    }
  }

  public static void setFilter(ImageFilter filter) {
    if (!Registry.is("color.blindness.icon.filter")) {
      filter = null;
    }
    if (IMAGE_FILTER != filter) {
      IMAGE_FILTER = filter;
      clearCache();
    }
  }

  private static void clearCache() {
    ourIconsCache.clear();
    ourIcon2DisabledIcon.clear();
  }

  // TODO[kb] support iconsets
  // public static Icon getIcon(@NotNull final String path, @NotNull final String darkVariantPath) {
  //  return new InvariantIcon(getIcon(path), getIcon(darkVariantPath));
  // }

  @NotNull
  public static Icon getIcon(@NonNls @NotNull final String path) {
    Class callerClass = ReflectionUtil.getGrandCallerClass();

    assert callerClass != null : path;
    return getIcon(path, callerClass);
  }

  @Nullable
  private static Icon getReflectiveIcon(@NotNull String path, ClassLoader classLoader) {
    try {
      @NonNls String pckg = path.startsWith("AllIcons.") ? "com.intellij.icons." : "icons.";
      Class cur =
          Class.forName(
              pckg + path.substring(0, path.lastIndexOf('.')).replace('.', '$'), true, classLoader);
      Field field = cur.getField(path.substring(path.lastIndexOf('.') + 1));

      return (Icon) field.get(null);
    } catch (Exception e) {
      return null;
    }
  }

  @Nullable
  /**
   * Might return null if icon was not found. Use only if you expected null return value, otherwise
   * see {@link IconLoader#getIcon(java.lang.String)}
   */
  public static Icon findIcon(@NonNls @NotNull String path) {
    Class callerClass = ReflectionUtil.getGrandCallerClass();
    if (callerClass == null) return null;
    return findIcon(path, callerClass);
  }

  @NotNull
  public static Icon getIcon(@NotNull String path, @NotNull final Class aClass) {
    final Icon icon = findIcon(path, aClass);
    if (icon == null) {
      LOG.error("Icon cannot be found in '" + path + "', aClass='" + aClass + "'");
    }
    return icon;
  }

  public static void activate() {
    ourIsActivated = true;
  }

  private static boolean isLoaderDisabled() {
    return !ourIsActivated;
  }

  /**
   * Might return null if icon was not found. Use only if you expected null return value, otherwise
   * see {@link IconLoader#getIcon(java.lang.String, java.lang.Class)}
   */
  @Nullable
  public static Icon findIcon(@NotNull final String path, @NotNull final Class aClass) {
    return findIcon(path, aClass, false);
  }

  @Nullable
  public static Icon findIcon(
      @NotNull String path, @NotNull final Class aClass, boolean computeNow) {
    path = undeprecate(path);
    if (isReflectivePath(path)) return getReflectiveIcon(path, aClass.getClassLoader());

    URL myURL = aClass.getResource(path);
    if (myURL == null) {
      if (STRICT) throw new RuntimeException("Can't find icon in '" + path + "' near " + aClass);
      return null;
    }
    return findIcon(myURL);
  }

  @NotNull
  private static String undeprecate(@NotNull String path) {
    String replacement = ourDeprecatedIconsReplacements.get(path);
    return replacement == null ? path : replacement;
  }

  private static boolean isReflectivePath(@NotNull String path) {
    List<String> paths = StringUtil.split(path, ".");
    return paths.size() > 1 && paths.get(0).endsWith("Icons");
  }

  @Nullable
  public static Icon findIcon(URL url) {
    return findIcon(url, true);
  }

  @Nullable
  public static Icon findIcon(URL url, boolean useCache) {
    if (url == null) {
      return null;
    }
    CachedImageIcon icon = ourIconsCache.get(url);
    if (icon == null) {
      icon = new CachedImageIcon(url);
      if (useCache) {
        icon = ConcurrencyUtil.cacheOrGet(ourIconsCache, url, icon);
      }
    }
    return icon;
  }

  @Nullable
  public static Icon findIcon(@NotNull String path, @NotNull ClassLoader classLoader) {
    path = undeprecate(path);
    if (isReflectivePath(path)) return getReflectiveIcon(path, classLoader);
    if (!StringUtil.startsWithChar(path, '/')) return null;

    final URL url = classLoader.getResource(path.substring(1));
    return findIcon(url);
  }

  @Nullable
  private static Icon checkIcon(final Image image, @NotNull URL url) {
    if (image == null
        || image.getHeight(LabelHolder.ourFakeComponent) < 1) { // image wasn't loaded or broken
      return null;
    }

    final Icon icon = getIcon(image);
    if (icon != null && !isGoodSize(icon)) {
      LOG.error("Invalid icon: " + url); // # 22481
      return EMPTY_ICON;
    }
    return icon;
  }

  public static boolean isGoodSize(@NotNull final Icon icon) {
    return icon.getIconWidth() > 0 && icon.getIconHeight() > 0;
  }

  /**
   * Gets (creates if necessary) disabled icon based on the passed one.
   *
   * @return <code>ImageIcon</code> constructed from disabled image of passed icon.
   */
  @Nullable
  public static Icon getDisabledIcon(Icon icon) {
    if (icon instanceof LazyIcon) icon = ((LazyIcon) icon).getOrComputeIcon();
    if (icon == null) return null;

    Icon disabledIcon = ourIcon2DisabledIcon.get(icon);
    if (disabledIcon == null) {
      if (!isGoodSize(icon)) {
        LOG.error(icon); // # 22481
        return EMPTY_ICON;
      }
      final int scale = UIUtil.isRetina() ? 2 : 1;
      @SuppressWarnings("UndesirableClassUsage")
      BufferedImage image =
          new BufferedImage(
              scale * icon.getIconWidth(),
              scale * icon.getIconHeight(),
              BufferedImage.TYPE_INT_ARGB);
      final Graphics2D graphics = image.createGraphics();

      graphics.setColor(UIUtil.TRANSPARENT_COLOR);
      graphics.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight());
      graphics.scale(scale, scale);
      icon.paintIcon(LabelHolder.ourFakeComponent, graphics, 0, 0);

      graphics.dispose();

      Image img = ImageUtil.filter(image, UIUtil.getGrayFilter());
      if (UIUtil.isRetina()) img = RetinaImage.createFrom(img, 2, ImageLoader.ourComponent);

      disabledIcon = new JBImageIcon(img);
      ourIcon2DisabledIcon.put(icon, disabledIcon);
    }
    return disabledIcon;
  }

  public static Icon getTransparentIcon(@NotNull final Icon icon) {
    return getTransparentIcon(icon, 0.5f);
  }

  public static Icon getTransparentIcon(@NotNull final Icon icon, final float alpha) {
    return new Icon() {
      @Override
      public int getIconHeight() {
        return icon.getIconHeight();
      }

      @Override
      public int getIconWidth() {
        return icon.getIconWidth();
      }

      @Override
      public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
        final Graphics2D g2 = (Graphics2D) g;
        final Composite saveComposite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
        icon.paintIcon(c, g2, x, y);
        g2.setComposite(saveComposite);
      }
    };
  }

  private static final class CachedImageIcon implements ScalableIcon {
    private Object myRealIcon;
    @NotNull private final URL myUrl;
    private boolean dark;
    private float scale;
    private ImageFilter filter;
    private HashMap<Float, Icon> scaledIcons;

    public CachedImageIcon(@NotNull URL url) {
      myUrl = url;
      dark = USE_DARK_ICONS;
      scale = SCALE;
      filter = IMAGE_FILTER;
    }

    @NotNull
    private synchronized Icon getRealIcon() {
      if (isLoaderDisabled()
          && (myRealIcon == null
              || dark != USE_DARK_ICONS
              || scale != SCALE
              || filter != IMAGE_FILTER)) return EMPTY_ICON;

      if (dark != USE_DARK_ICONS || scale != SCALE || filter != IMAGE_FILTER) {
        myRealIcon = null;
        dark = USE_DARK_ICONS;
        scale = SCALE;
        filter = IMAGE_FILTER;
      }
      Object realIcon = myRealIcon;
      if (realIcon instanceof Icon) return (Icon) realIcon;

      Icon icon;
      if (realIcon instanceof Reference) {
        icon = ((Reference<Icon>) realIcon).get();
        if (icon != null) return icon;
      }

      Image image = ImageLoader.loadFromUrl(myUrl, true, filter);
      icon = checkIcon(image, myUrl);

      if (icon != null) {
        if (icon.getIconWidth() < 50 && icon.getIconHeight() < 50) {
          realIcon = icon;
        } else {
          realIcon = new SoftReference<Icon>(icon);
        }
        myRealIcon = realIcon;
      }

      return icon == null ? EMPTY_ICON : icon;
    }

    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
      getRealIcon().paintIcon(c, g, x, y);
    }

    @Override
    public int getIconWidth() {
      return getRealIcon().getIconWidth();
    }

    @Override
    public int getIconHeight() {
      return getRealIcon().getIconHeight();
    }

    @Override
    public String toString() {
      return myUrl.toString();
    }

    @Override
    public Icon scale(float scaleFactor) {
      if (scaleFactor == 1f) {
        return this;
      }
      if (scaledIcons == null) {
        scaledIcons = new HashMap<Float, Icon>(1);
      }

      Icon result = scaledIcons.get(scaleFactor);
      if (result != null) {
        return result;
      }

      final Image image =
          ImageLoader.loadFromUrl(myUrl, UIUtil.isUnderDarcula(), scaleFactor >= 1.5f, filter);
      if (image != null) {
        int width = (int) (getIconWidth() * scaleFactor);
        int height = (int) (getIconHeight() * scaleFactor);
        final BufferedImage resizedImage =
            Scalr.resize(
                ImageUtil.toBufferedImage(image), Scalr.Method.ULTRA_QUALITY, width, height);
        result = getIcon(resizedImage);
        scaledIcons.put(scaleFactor, result);
        return result;
      }

      return this;
    }
  }

  public abstract static class LazyIcon implements Icon {
    private boolean myWasComputed;
    private Icon myIcon;
    private boolean isDarkVariant = USE_DARK_ICONS;
    private float scale = SCALE;
    private ImageFilter filter = IMAGE_FILTER;

    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
      final Icon icon = getOrComputeIcon();
      if (icon != null) {
        icon.paintIcon(c, g, x, y);
      }
    }

    @Override
    public int getIconWidth() {
      final Icon icon = getOrComputeIcon();
      return icon != null ? icon.getIconWidth() : 0;
    }

    @Override
    public int getIconHeight() {
      final Icon icon = getOrComputeIcon();
      return icon != null ? icon.getIconHeight() : 0;
    }

    protected final synchronized Icon getOrComputeIcon() {
      if (!myWasComputed
          || isDarkVariant != USE_DARK_ICONS
          || scale != SCALE
          || filter != IMAGE_FILTER) {
        isDarkVariant = USE_DARK_ICONS;
        scale = SCALE;
        filter = IMAGE_FILTER;
        myWasComputed = true;
        myIcon = compute();
      }

      return myIcon;
    }

    public final void load() {
      getIconWidth();
    }

    protected abstract Icon compute();
  }

  private static class LabelHolder {
    /**
     * To get disabled icon with paint it into the image. Some icons require not null component to
     * paint.
     */
    private static final JComponent ourFakeComponent = new JLabel();
  }
}
public abstract class MultiplePsiFilesPerDocumentFileViewProvider
    extends SingleRootFileViewProvider {
  private final ConcurrentMap<Language, PsiFileImpl> myRoots =
      ContainerUtil.newConcurrentMap(1, 0.75f, 1);
  private MultiplePsiFilesPerDocumentFileViewProvider myOriginal = null;

  public MultiplePsiFilesPerDocumentFileViewProvider(
      PsiManager manager, VirtualFile virtualFile, boolean eventSystemEnabled) {
    super(manager, virtualFile, eventSystemEnabled, Language.ANY);
  }

  @Override
  @NotNull
  public abstract Language getBaseLanguage();

  @Override
  @NotNull
  public List<PsiFile> getAllFiles() {
    final List<PsiFile> roots = new ArrayList<PsiFile>();
    for (Language language : getLanguages()) {
      PsiFile psi = getPsi(language);
      if (psi != null) roots.add(psi);
    }
    final PsiFile base = getPsi(getBaseLanguage());
    if (!roots.isEmpty() && roots.get(0) != base) {
      roots.remove(base);
      roots.add(0, base);
    }
    return roots;
  }

  protected void removeFile(final Language language) {
    PsiFileImpl file = myRoots.remove(language);
    if (file != null) {
      file.markInvalidated();
    }
  }

  @Override
  protected PsiFile getPsiInner(@NotNull final Language target) {
    PsiFileImpl file = myRoots.get(target);
    if (file == null) {
      if (isPhysical()) {
        VirtualFile virtualFile = getVirtualFile();
        if (isIgnored()) return null;
        VirtualFile parent = virtualFile.getParent();
        if (parent != null) {
          getManager().findDirectory(parent);
        }
      }
      if (target != getBaseLanguage() && !getLanguages().contains(target)) {
        return null;
      }
      file = (PsiFileImpl) createFile(target);
      if (file == null) return null;
      if (myOriginal != null) {
        final PsiFile originalFile = myOriginal.getPsi(target);
        if (originalFile != null) {
          file.setOriginalFile(originalFile);
        }
      }
      file = ConcurrencyUtil.cacheOrGet(myRoots, target, file);
    }
    return file;
  }

  @Override
  public PsiFile getCachedPsi(@NotNull Language target) {
    return myRoots.get(target);
  }

  @NotNull
  @Override
  public FileElement[] getKnownTreeRoots() {
    List<FileElement> files = new ArrayList<FileElement>(myRoots.size());
    for (PsiFile file : myRoots.values()) {
      final FileElement treeElement = ((PsiFileImpl) file).getTreeElement();
      if (treeElement != null) {
        files.add(treeElement);
      }
    }

    return files.toArray(new FileElement[files.size()]);
  }

  @TestOnly
  public void checkAllTreesEqual() {
    Collection<PsiFileImpl> roots = myRoots.values();
    PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getManager().getProject());
    documentManager.commitAllDocuments();
    for (PsiFile root : roots) {
      Document document = documentManager.getDocument(root);
      PsiDocumentManagerBase.checkConsistency(root, document);
      assert root.getText().equals(document.getText());
    }
  }

  @NotNull
  @Override
  public final MultiplePsiFilesPerDocumentFileViewProvider createCopy(
      @NotNull final VirtualFile fileCopy) {
    final MultiplePsiFilesPerDocumentFileViewProvider copy = cloneInner(fileCopy);
    copy.myOriginal = myOriginal == null ? this : myOriginal;
    return copy;
  }

  protected abstract MultiplePsiFilesPerDocumentFileViewProvider cloneInner(VirtualFile fileCopy);

  @Override
  @Nullable
  public PsiElement findElementAt(int offset, @NotNull Class<? extends Language> lang) {
    final PsiFile mainRoot = getPsi(getBaseLanguage());
    PsiElement ret = null;
    for (final Language language : getLanguages()) {
      if (!ReflectionUtil.isAssignable(lang, language.getClass())) continue;
      if (lang.equals(Language.class) && !getLanguages().contains(language)) continue;

      final PsiFile psiRoot = getPsi(language);
      final PsiElement psiElement = findElementAt(psiRoot, offset);
      if (psiElement == null || psiElement instanceof OuterLanguageElement) continue;
      if (ret == null || psiRoot != mainRoot) {
        ret = psiElement;
      }
    }
    return ret;
  }

  @Override
  @Nullable
  public PsiElement findElementAt(int offset) {
    return findElementAt(offset, Language.class);
  }

  @Override
  @Nullable
  public PsiReference findReferenceAt(int offset) {
    TextRange minRange = new TextRange(0, getContents().length());
    PsiReference ret = null;
    for (final Language language : getLanguages()) {
      final PsiElement psiRoot = getPsi(language);
      final PsiReference reference =
          SharedPsiElementImplUtil.findReferenceAt(psiRoot, offset, language);
      if (reference == null) continue;
      final TextRange textRange =
          reference
              .getRangeInElement()
              .shiftRight(reference.getElement().getTextRange().getStartOffset());
      if (minRange.contains(textRange) && !textRange.contains(minRange)) {
        minRange = textRange;
        ret = reference;
      }
    }
    return ret;
  }

  @Override
  public void contentsSynchronized() {
    super.contentsSynchronized();
    Set<Language> languages = getLanguages();
    for (Iterator<Map.Entry<Language, PsiFileImpl>> iterator = myRoots.entrySet().iterator();
        iterator.hasNext(); ) {
      Map.Entry<Language, PsiFileImpl> entry = iterator.next();
      if (!languages.contains(entry.getKey())) {
        PsiFileImpl file = entry.getValue();
        iterator.remove();
        file.markInvalidated();
      }
    }
  }

  @Override
  public void markInvalidated() {
    for (PsiFileImpl file : myRoots.values()) {
      file.markInvalidated();
    }
    super.markInvalidated();
  }
}
public class LookupImpl extends LightweightHint implements LookupEx, Disposable, WeighingContext {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl");

  private final LookupOffsets myOffsets;
  private final Project myProject;
  private final Editor myEditor;
  private final JBList myList =
      new JBList(new CollectionListModel<LookupElement>()) {
        @Override
        protected void processKeyEvent(@NotNull final KeyEvent e) {
          final char keyChar = e.getKeyChar();
          if (keyChar == KeyEvent.VK_ENTER || keyChar == KeyEvent.VK_TAB) {
            IdeFocusManager.getInstance(myProject)
                .requestFocus(myEditor.getContentComponent(), true)
                .doWhenDone(
                    new Runnable() {
                      @Override
                      public void run() {
                        IdeEventQueue.getInstance().getKeyEventDispatcher().dispatchKeyEvent(e);
                      }
                    });
            return;
          }

          super.processKeyEvent(e);
        }

        @NotNull
        @Override
        protected ExpandableItemsHandler<Integer> createExpandableItemsHandler() {
          return new CompletionExtender(this);
        }
      };
  final LookupCellRenderer myCellRenderer;

  private final List<LookupListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();

  private long myStampShown = 0;
  private boolean myShown = false;
  private boolean myDisposed = false;
  private boolean myHidden = false;
  private boolean mySelectionTouched;
  private FocusDegree myFocusDegree = FocusDegree.FOCUSED;
  private volatile boolean myCalculating;
  private final Advertiser myAdComponent;
  volatile int myLookupTextWidth = 50;
  private boolean myChangeGuard;
  private volatile LookupArranger myArranger;
  private LookupArranger myPresentableArranger;
  private final Map<LookupElement, PrefixMatcher> myMatchers =
      ContainerUtil.newConcurrentMap(ContainerUtil.<LookupElement>identityStrategy());
  private final Map<LookupElement, Font> myCustomFonts =
      ContainerUtil.createConcurrentWeakMap(
          10,
          0.75f,
          Runtime.getRuntime().availableProcessors(),
          ContainerUtil.<LookupElement>identityStrategy());
  private boolean myStartCompletionWhenNothingMatches;
  boolean myResizePending;
  private boolean myFinishing;
  boolean myUpdating;
  private LookupUi myUi;

  public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger) {
    super(new JPanel(new BorderLayout()));
    setForceShowAsPopup(true);
    setCancelOnClickOutside(false);
    setResizable(true);
    AbstractPopup.suppressMacCornerFor(getComponent());

    myProject = project;
    myEditor = editor;
    myArranger = arranger;
    myPresentableArranger = arranger;

    myCellRenderer = new LookupCellRenderer(this);
    myList.setCellRenderer(myCellRenderer);

    myList.setFocusable(false);
    myList.setFixedCellWidth(50);

    myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR);

    myList.getExpandableItemsHandler();

    myAdComponent = new Advertiser();

    myOffsets = new LookupOffsets(editor);

    final CollectionListModel<LookupElement> model = getListModel();
    addEmptyItem(model);
    updateListHeight(model);

    addListeners();
  }

  private CollectionListModel<LookupElement> getListModel() {
    //noinspection unchecked
    return (CollectionListModel<LookupElement>) myList.getModel();
  }

  public void setArranger(LookupArranger arranger) {
    myArranger = arranger;
  }

  public FocusDegree getFocusDegree() {
    return myFocusDegree;
  }

  @Override
  public boolean isFocused() {
    return getFocusDegree() == FocusDegree.FOCUSED;
  }

  public void setFocusDegree(FocusDegree focusDegree) {
    myFocusDegree = focusDegree;
  }

  public boolean isCalculating() {
    return myCalculating;
  }

  public void setCalculating(final boolean calculating) {
    myCalculating = calculating;
    if (myUi != null) {
      myUi.setCalculating(calculating);
    }
  }

  public void markSelectionTouched() {
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      ApplicationManager.getApplication().assertIsDispatchThread();
    }
    mySelectionTouched = true;
    myList.repaint();
  }

  @TestOnly
  public void setSelectionTouched(boolean selectionTouched) {
    mySelectionTouched = selectionTouched;
  }

  public void resort(boolean addAgain) {
    final List<LookupElement> items = getItems();

    synchronized (myList) {
      myPresentableArranger.prefixChanged(this);
      getListModel().removeAll();
    }

    if (addAgain) {
      for (final LookupElement item : items) {
        addItem(item, itemMatcher(item));
      }
    }
    refreshUi(true, true);
  }

  public boolean addItem(LookupElement item, PrefixMatcher matcher) {
    LookupElementPresentation presentation = renderItemApproximately(item);
    if (containsDummyIdentifier(presentation.getItemText())
        || containsDummyIdentifier(presentation.getTailText())
        || containsDummyIdentifier(presentation.getTypeText())) {
      return false;
    }

    myMatchers.put(item, matcher);
    updateLookupWidth(item, presentation);
    synchronized (myList) {
      myArranger.addElement(this, item, presentation);
    }
    return true;
  }

  private static boolean containsDummyIdentifier(@Nullable final String s) {
    return s != null && s.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED);
  }

  public void updateLookupWidth(LookupElement item) {
    updateLookupWidth(item, renderItemApproximately(item));
  }

  private void updateLookupWidth(LookupElement item, LookupElementPresentation presentation) {
    final Font customFont = myCellRenderer.getFontAbleToDisplay(presentation);
    if (customFont != null) {
      myCustomFonts.put(item, customFont);
    }
    int maxWidth = myCellRenderer.updateMaximumWidth(presentation, item);
    myLookupTextWidth = Math.max(maxWidth, myLookupTextWidth);
  }

  @Nullable
  public Font getCustomFont(LookupElement item, boolean bold) {
    Font font = myCustomFonts.get(item);
    return font == null ? null : bold ? font.deriveFont(Font.BOLD) : font;
  }

  public void requestResize() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    myResizePending = true;
  }

  public Collection<LookupElementAction> getActionsFor(LookupElement element) {
    final CollectConsumer<LookupElementAction> consumer =
        new CollectConsumer<LookupElementAction>();
    for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) {
      provider.fillActions(element, this, consumer);
    }
    if (!consumer.getResult().isEmpty()) {
      consumer.consume(new ShowHideIntentionIconLookupAction());
    }
    return consumer.getResult();
  }

  public JList getList() {
    return myList;
  }

  @Override
  public List<LookupElement> getItems() {
    synchronized (myList) {
      return ContainerUtil.findAll(
          getListModel().toList(),
          new Condition<LookupElement>() {
            @Override
            public boolean value(LookupElement element) {
              return !(element instanceof EmptyLookupItem);
            }
          });
    }
  }

  public String getAdditionalPrefix() {
    return myOffsets.getAdditionalPrefix();
  }

  void appendPrefix(char c) {
    checkValid();
    myOffsets.appendPrefix(c);
    synchronized (myList) {
      myPresentableArranger.prefixChanged(this);
    }
    requestResize();
    refreshUi(false, true);
    ensureSelectionVisible(true);
  }

  public void setStartCompletionWhenNothingMatches(boolean startCompletionWhenNothingMatches) {
    myStartCompletionWhenNothingMatches = startCompletionWhenNothingMatches;
  }

  public boolean isStartCompletionWhenNothingMatches() {
    return myStartCompletionWhenNothingMatches;
  }

  public void ensureSelectionVisible(boolean forceTopSelection) {
    if (isSelectionVisible() && !forceTopSelection) {
      return;
    }

    if (!forceTopSelection) {
      ListScrollingUtil.ensureIndexIsVisible(myList, myList.getSelectedIndex(), 1);
      return;
    }

    // selected item should be at the top of the visible list
    int top = myList.getSelectedIndex();
    if (top > 0) {
      top--; // show one element above the selected one to give the hint that there are more
             // available via scrolling
    }

    int firstVisibleIndex = myList.getFirstVisibleIndex();
    if (firstVisibleIndex == top) {
      return;
    }

    ListScrollingUtil.ensureRangeIsVisible(
        myList, top, top + myList.getLastVisibleIndex() - firstVisibleIndex);
  }

  boolean truncatePrefix(boolean preserveSelection) {
    if (!myOffsets.truncatePrefix()) {
      return false;
    }

    if (preserveSelection) {
      markSelectionTouched();
    }

    boolean shouldUpdate;
    synchronized (myList) {
      shouldUpdate = myPresentableArranger == myArranger;
      myPresentableArranger.prefixChanged(this);
    }
    requestResize();
    if (shouldUpdate) {
      refreshUi(false, true);
      ensureSelectionVisible(true);
    }

    return true;
  }

  private boolean updateList(boolean onExplicitAction, boolean reused) {
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      ApplicationManager.getApplication().assertIsDispatchThread();
    }
    checkValid();

    CollectionListModel<LookupElement> listModel = getListModel();

    Pair<List<LookupElement>, Integer> pair;
    synchronized (myList) {
      pair = myPresentableArranger.arrangeItems(this, onExplicitAction || reused);
    }

    List<LookupElement> items = pair.first;
    Integer toSelect = pair.second;
    if (toSelect == null || toSelect < 0 || items.size() > 0 && toSelect >= items.size()) {
      LOG.error(
          "Arranger "
              + myPresentableArranger
              + " returned invalid selection index="
              + toSelect
              + "; items="
              + items);
      toSelect = 0;
    }

    myOffsets.checkMinPrefixLengthChanges(items, this);
    List<LookupElement> oldModel = listModel.toList();

    listModel.removeAll();
    if (!items.isEmpty()) {
      listModel.add(items);
    } else {
      addEmptyItem(listModel);
    }

    updateListHeight(listModel);

    myList.setSelectedIndex(toSelect);
    return !ContainerUtil.equalsIdentity(oldModel, items);
  }

  private boolean isSelectionVisible() {
    return ListScrollingUtil.isIndexFullyVisible(myList, myList.getSelectedIndex());
  }

  private boolean checkReused() {
    synchronized (myList) {
      if (myPresentableArranger != myArranger) {
        myPresentableArranger = myArranger;
        myOffsets.clearAdditionalPrefix();
        myPresentableArranger.prefixChanged(this);
        return true;
      }
      return false;
    }
  }

  private void updateListHeight(ListModel model) {
    myList.setFixedCellHeight(
        myCellRenderer
            .getListCellRendererComponent(myList, model.getElementAt(0), 0, false, false)
            .getPreferredSize()
            .height);

    myList.setVisibleRowCount(
        Math.min(model.getSize(), UISettings.getInstance().MAX_LOOKUP_LIST_HEIGHT));
  }

  private void addEmptyItem(CollectionListModel<LookupElement> model) {
    LookupItem<String> item =
        new EmptyLookupItem(
            myCalculating ? " " : LangBundle.message("completion.no.suggestions"), false);
    myMatchers.put(item, new CamelHumpMatcher(""));
    model.add(item);

    updateLookupWidth(item);
    requestResize();
  }

  private static LookupElementPresentation renderItemApproximately(LookupElement item) {
    final LookupElementPresentation p = new LookupElementPresentation();
    item.renderElement(p);
    return p;
  }

  @NotNull
  @Override
  public String itemPattern(@NotNull LookupElement element) {
    String prefix = itemMatcher(element).getPrefix();
    String additionalPrefix = getAdditionalPrefix();
    return additionalPrefix.isEmpty() ? prefix : prefix + additionalPrefix;
  }

  @Override
  @NotNull
  public PrefixMatcher itemMatcher(@NotNull LookupElement item) {
    PrefixMatcher matcher = itemMatcherNullable(item);
    if (matcher == null) {
      throw new AssertionError("Item not in lookup: item=" + item + "; lookup items=" + getItems());
    }
    return matcher;
  }

  public PrefixMatcher itemMatcherNullable(LookupElement item) {
    return myMatchers.get(item);
  }

  public void finishLookup(final char completionChar) {
    finishLookup(completionChar, (LookupElement) myList.getSelectedValue());
  }

  public void finishLookup(char completionChar, @Nullable final LookupElement item) {
    //noinspection deprecation,unchecked
    if (item == null
        || item instanceof EmptyLookupItem
        || item.getObject() instanceof DeferredUserLookupValue
            && item.as(LookupItem.CLASS_CONDITION_KEY) != null
            && !((DeferredUserLookupValue) item.getObject())
                .handleUserSelection(item.as(LookupItem.CLASS_CONDITION_KEY), myProject)) {
      doHide(false, true);
      fireItemSelected(null, completionChar);
      return;
    }

    if (myDisposed) { // DeferredUserLookupValue could close us in any way
      return;
    }

    final PsiFile file = getPsiFile();
    boolean writableOk =
        file == null || FileModificationService.getInstance().prepareFileForWrite(file);
    if (myDisposed) { // ensureFilesWritable could close us by showing a dialog
      return;
    }

    if (!writableOk) {
      doHide(false, true);
      fireItemSelected(null, completionChar);
      return;
    }

    final String prefix = itemPattern(item);
    boolean plainMatch =
        ContainerUtil.or(
            item.getAllLookupStrings(),
            new Condition<String>() {
              @Override
              public boolean value(String s) {
                return StringUtil.containsIgnoreCase(s, prefix);
              }
            });
    if (!plainMatch) {
      FeatureUsageTracker.getInstance()
          .triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS);
    }

    myFinishing = true;
    ApplicationManager.getApplication()
        .runWriteAction(
            new Runnable() {
              public void run() {
                myEditor.getDocument().startGuardedBlockChecking();
                try {
                  insertLookupString(item, getPrefixLength(item));
                } finally {
                  myEditor.getDocument().stopGuardedBlockChecking();
                }
              }
            });

    if (myDisposed) { // any document listeners could close us
      return;
    }

    doHide(false, true);

    fireItemSelected(item, completionChar);
  }

  public int getPrefixLength(LookupElement item) {
    return myOffsets.getPrefixLength(item, this);
  }

  private void insertLookupString(LookupElement item, final int prefix) {
    final String lookupString = getCaseCorrectedLookupString(item);

    final Editor hostEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
    hostEditor
        .getCaretModel()
        .runForEachCaret(
            new CaretAction() {
              @Override
              public void perform(Caret caret) {
                EditorModificationUtil.deleteSelectedText(hostEditor);
                final int caretOffset = hostEditor.getCaretModel().getOffset();
                int lookupStart = Math.max(caretOffset - prefix, 0);

                int len = hostEditor.getDocument().getTextLength();
                LOG.assertTrue(
                    lookupStart >= 0 && lookupStart <= len,
                    "ls: "
                        + lookupStart
                        + " caret: "
                        + caretOffset
                        + " prefix:"
                        + prefix
                        + " doc: "
                        + len);
                LOG.assertTrue(
                    caretOffset >= 0 && caretOffset <= len, "co: " + caretOffset + " doc: " + len);

                hostEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString);

                int offset = lookupStart + lookupString.length();
                hostEditor.getCaretModel().moveToOffset(offset);
                hostEditor.getSelectionModel().removeSelection();
              }
            });

    myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
  }

  private String getCaseCorrectedLookupString(LookupElement item) {
    String lookupString = item.getLookupString();
    if (item.isCaseSensitive()) {
      return lookupString;
    }

    final String prefix = itemPattern(item);
    final int length = prefix.length();
    if (length == 0 || !itemMatcher(item).prefixMatches(prefix)) return lookupString;
    boolean isAllLower = true;
    boolean isAllUpper = true;
    boolean sameCase = true;
    for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) {
      final char c = prefix.charAt(i);
      boolean isLower = Character.isLowerCase(c);
      boolean isUpper = Character.isUpperCase(c);
      // do not take this kind of symbols into account ('_', '@', etc.)
      if (!isLower && !isUpper) continue;
      isAllLower = isAllLower && isLower;
      isAllUpper = isAllUpper && isUpper;
      sameCase = sameCase && isLower == Character.isLowerCase(lookupString.charAt(i));
    }
    if (sameCase) return lookupString;
    if (isAllLower) return lookupString.toLowerCase();
    if (isAllUpper) return StringUtil.toUpperCase(lookupString);
    return lookupString;
  }

  @Override
  public int getLookupStart() {
    return myOffsets.getLookupStart(disposeTrace);
  }

  public int getLookupOriginalStart() {
    return myOffsets.getLookupOriginalStart();
  }

  public boolean performGuardedChange(Runnable change) {
    checkValid();
    assert !myChangeGuard : "already in change";

    myEditor.getDocument().startGuardedBlockChecking();
    myChangeGuard = true;
    boolean result;
    try {
      result = myOffsets.performGuardedChange(change);
    } finally {
      myEditor.getDocument().stopGuardedBlockChecking();
      myChangeGuard = false;
    }
    if (!result || myDisposed) {
      hide();
      return false;
    }
    if (isVisible()) {
      HintManagerImpl.updateLocation(this, myEditor, myUi.calculatePosition().getLocation());
    }
    checkValid();
    return true;
  }

  @Override
  public boolean vetoesHiding() {
    return myChangeGuard;
  }

  public boolean isAvailableToUser() {
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      return myShown;
    }
    return isVisible();
  }

  public boolean isShown() {
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      ApplicationManager.getApplication().assertIsDispatchThread();
    }
    return myShown;
  }

  public boolean showLookup() {
    ApplicationManager.getApplication().assertIsDispatchThread();
    checkValid();
    LOG.assertTrue(!myShown);
    myShown = true;
    myStampShown = System.currentTimeMillis();

    if (ApplicationManager.getApplication().isUnitTestMode()) return true;

    if (!myEditor.getContentComponent().isShowing()) {
      hide();
      return false;
    }

    myAdComponent.showRandomText();

    myUi = new LookupUi(this, myAdComponent, myList, myProject);
    myUi.setCalculating(myCalculating);
    Point p = myUi.calculatePosition().getLocation();
    try {
      HintManagerImpl.getInstanceImpl()
          .showEditorHint(
              this,
              myEditor,
              p,
              HintManager.HIDE_BY_ESCAPE | HintManager.UPDATE_BY_SCROLLING,
              0,
              false,
              HintManagerImpl.createHintHint(myEditor, p, this, HintManager.UNDER)
                  .setAwtTooltip(false));
    } catch (Exception e) {
      LOG.error(e);
    }

    if (!isVisible() || !myList.isShowing()) {
      hide();
      return false;
    }

    DaemonCodeAnalyzer.getInstance(myProject).disableUpdateByTimer(this);

    return true;
  }

  public Advertiser getAdvertiser() {
    return myAdComponent;
  }

  public boolean mayBeNoticed() {
    return myStampShown > 0 && System.currentTimeMillis() - myStampShown > 300;
  }

  private void addListeners() {
    myEditor
        .getDocument()
        .addDocumentListener(
            new DocumentAdapter() {
              @Override
              public void documentChanged(DocumentEvent e) {
                if (!myChangeGuard && !myFinishing) {
                  hide();
                }
              }
            },
            this);

    final CaretListener caretListener =
        new CaretAdapter() {
          @Override
          public void caretPositionChanged(CaretEvent e) {
            if (!myChangeGuard && !myFinishing) {
              hide();
            }
          }
        };
    final SelectionListener selectionListener =
        new SelectionListener() {
          @Override
          public void selectionChanged(final SelectionEvent e) {
            if (!myChangeGuard && !myFinishing) {
              hide();
            }
          }
        };
    final EditorMouseListener mouseListener =
        new EditorMouseAdapter() {
          @Override
          public void mouseClicked(EditorMouseEvent e) {
            e.consume();
            hide();
          }
        };

    myEditor.getCaretModel().addCaretListener(caretListener);
    myEditor.getSelectionModel().addSelectionListener(selectionListener);
    myEditor.addEditorMouseListener(mouseListener);
    Disposer.register(
        this,
        new Disposable() {
          @Override
          public void dispose() {
            myEditor.getCaretModel().removeCaretListener(caretListener);
            myEditor.getSelectionModel().removeSelectionListener(selectionListener);
            myEditor.removeEditorMouseListener(mouseListener);
          }
        });

    JComponent editorComponent = myEditor.getContentComponent();
    if (editorComponent.isShowing()) {
      Disposer.register(
          this,
          new UiNotifyConnector(
              editorComponent,
              new Activatable() {
                @Override
                public void showNotify() {}

                @Override
                public void hideNotify() {
                  hideLookup(false);
                }
              }));
    }

    myList.addListSelectionListener(
        new ListSelectionListener() {
          private LookupElement oldItem = null;

          @Override
          public void valueChanged(@NotNull ListSelectionEvent e) {
            if (!myUpdating) {
              final LookupElement item = getCurrentItem();
              fireCurrentItemChanged(oldItem, item);
              oldItem = item;
            }
          }
        });

    new ClickListener() {
      @Override
      public boolean onClick(@NotNull MouseEvent e, int clickCount) {
        setFocusDegree(FocusDegree.FOCUSED);
        markSelectionTouched();

        if (clickCount == 2) {
          CommandProcessor.getInstance()
              .executeCommand(
                  myProject,
                  new Runnable() {
                    @Override
                    public void run() {
                      finishLookup(NORMAL_SELECT_CHAR);
                    }
                  },
                  "",
                  null);
        }
        return true;
      }
    }.installOn(myList);
  }

  @Override
  @Nullable
  public LookupElement getCurrentItem() {
    LookupElement item = (LookupElement) myList.getSelectedValue();
    return item instanceof EmptyLookupItem ? null : item;
  }

  @Override
  public void setCurrentItem(LookupElement item) {
    markSelectionTouched();
    myList.setSelectedValue(item, false);
  }

  @Override
  public void addLookupListener(LookupListener listener) {
    myListeners.add(listener);
  }

  @Override
  public void removeLookupListener(LookupListener listener) {
    myListeners.remove(listener);
  }

  @Override
  public Rectangle getCurrentItemBounds() {
    int index = myList.getSelectedIndex();
    if (index < 0) {
      LOG.error("No selected element, size=" + getListModel().getSize() + "; items" + getItems());
    }
    Rectangle itmBounds = myList.getCellBounds(index, index);
    if (itmBounds == null) {
      LOG.error("No bounds for " + index + "; size=" + getListModel().getSize());
      return null;
    }
    Point layeredPanePoint =
        SwingUtilities.convertPoint(myList, itmBounds.x, itmBounds.y, getComponent());
    itmBounds.x = layeredPanePoint.x;
    itmBounds.y = layeredPanePoint.y;
    return itmBounds;
  }

  public void fireItemSelected(@Nullable final LookupElement item, char completionChar) {
    PsiDocumentManager.getInstance(myProject).commitAllDocuments();

    if (!myListeners.isEmpty()) {
      LookupEvent event = new LookupEvent(this, item, completionChar);
      for (LookupListener listener : myListeners) {
        try {
          listener.itemSelected(event);
        } catch (Throwable e) {
          LOG.error(e);
        }
      }
    }
  }

  private void fireLookupCanceled(final boolean explicitly) {
    if (!myListeners.isEmpty()) {
      LookupEvent event = new LookupEvent(this, explicitly);
      for (LookupListener listener : myListeners) {
        try {
          listener.lookupCanceled(event);
        } catch (Throwable e) {
          LOG.error(e);
        }
      }
    }
  }

  private void fireCurrentItemChanged(
      @Nullable LookupElement oldItem, @Nullable LookupElement currentItem) {
    if (oldItem != currentItem && !myListeners.isEmpty()) {
      LookupEvent event = new LookupEvent(this, currentItem, (char) 0);
      for (LookupListener listener : myListeners) {
        listener.currentItemChanged(event);
      }
    }
  }

  public boolean fillInCommonPrefix(boolean explicitlyInvoked) {
    if (explicitlyInvoked) {
      setFocusDegree(FocusDegree.FOCUSED);
    }

    if (explicitlyInvoked && myCalculating) return false;
    if (!explicitlyInvoked && mySelectionTouched) return false;

    ListModel listModel = getListModel();
    if (listModel.getSize() <= 1) return false;

    if (listModel.getSize() == 0) return false;

    final LookupElement firstItem = (LookupElement) listModel.getElementAt(0);
    if (listModel.getSize() == 1 && firstItem instanceof EmptyLookupItem) return false;

    final PrefixMatcher firstItemMatcher = itemMatcher(firstItem);
    final String oldPrefix = firstItemMatcher.getPrefix();
    final String presentPrefix = oldPrefix + getAdditionalPrefix();
    String commonPrefix = getCaseCorrectedLookupString(firstItem);

    for (int i = 1; i < listModel.getSize(); i++) {
      LookupElement item = (LookupElement) listModel.getElementAt(i);
      if (item instanceof EmptyLookupItem) return false;
      if (!oldPrefix.equals(itemMatcher(item).getPrefix())) return false;

      final String lookupString = getCaseCorrectedLookupString(item);
      final int length = Math.min(commonPrefix.length(), lookupString.length());
      if (length < commonPrefix.length()) {
        commonPrefix = commonPrefix.substring(0, length);
      }

      for (int j = 0; j < length; j++) {
        if (commonPrefix.charAt(j) != lookupString.charAt(j)) {
          commonPrefix = lookupString.substring(0, j);
          break;
        }
      }

      if (commonPrefix.length() == 0 || commonPrefix.length() < presentPrefix.length()) {
        return false;
      }
    }

    if (commonPrefix.equals(presentPrefix)) {
      return false;
    }

    for (int i = 0; i < listModel.getSize(); i++) {
      LookupElement item = (LookupElement) listModel.getElementAt(i);
      if (!itemMatcher(item).cloneWithPrefix(commonPrefix).prefixMatches(item)) {
        return false;
      }
    }

    myOffsets.setInitialPrefix(presentPrefix, explicitlyInvoked);

    replacePrefix(presentPrefix, commonPrefix);
    return true;
  }

  public void replacePrefix(final String presentPrefix, final String newPrefix) {
    if (!performGuardedChange(
        new Runnable() {
          @Override
          public void run() {
            EditorModificationUtil.deleteSelectedText(myEditor);
            int offset = myEditor.getCaretModel().getOffset();
            final int start = offset - presentPrefix.length();
            myEditor.getDocument().replaceString(start, offset, newPrefix);

            Map<LookupElement, PrefixMatcher> newMatchers =
                new HashMap<LookupElement, PrefixMatcher>();
            for (LookupElement item : getItems()) {
              if (item.isValid()) {
                PrefixMatcher matcher = itemMatcher(item).cloneWithPrefix(newPrefix);
                if (matcher.prefixMatches(item)) {
                  newMatchers.put(item, matcher);
                }
              }
            }
            myMatchers.clear();
            myMatchers.putAll(newMatchers);

            myOffsets.clearAdditionalPrefix();

            myEditor.getCaretModel().moveToOffset(start + newPrefix.length());
          }
        })) {
      return;
    }
    synchronized (myList) {
      myPresentableArranger.prefixChanged(this);
    }
    refreshUi(true, true);
  }

  @Override
  @Nullable
  public PsiFile getPsiFile() {
    return PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
  }

  @Override
  public boolean isCompletion() {
    return myArranger instanceof CompletionLookupArranger;
  }

  @Override
  public PsiElement getPsiElement() {
    PsiFile file = getPsiFile();
    if (file == null) return null;

    int offset = getLookupStart();
    if (offset > 0) return file.findElementAt(offset - 1);

    return file.findElementAt(0);
  }

  @Override
  public Editor getEditor() {
    return myEditor;
  }

  @Override
  public boolean isPositionedAboveCaret() {
    return myUi != null && myUi.isPositionedAboveCaret();
  }

  @Override
  public boolean isSelectionTouched() {
    return mySelectionTouched;
  }

  @Override
  public List<String> getAdvertisements() {
    return myAdComponent.getAdvertisements();
  }

  @Override
  public void hide() {
    hideLookup(true);
  }

  public void hideLookup(boolean explicitly) {
    ApplicationManager.getApplication().assertIsDispatchThread();

    if (myHidden) return;

    doHide(true, explicitly);
  }

  private void doHide(final boolean fireCanceled, final boolean explicitly) {
    if (myDisposed) {
      LOG.error(disposeTrace);
    } else {
      myHidden = true;

      try {
        super.hide();

        Disposer.dispose(this);

        assert myDisposed;
      } catch (Throwable e) {
        LOG.error(e);
      }
    }

    if (fireCanceled) {
      fireLookupCanceled(explicitly);
    }
  }

  public void restorePrefix() {
    myOffsets.restorePrefix();
  }

  private static String staticDisposeTrace = null;
  private String disposeTrace = null;

  public static String getLastLookupDisposeTrace() {
    return staticDisposeTrace;
  }

  @Override
  public void dispose() {
    assert ApplicationManager.getApplication().isDispatchThread();
    assert myHidden;
    if (myDisposed) {
      LOG.error(disposeTrace);
      return;
    }

    myOffsets.disposeMarkers();
    myDisposed = true;
    disposeTrace = DebugUtil.currentStackTrace() + "\n============";
    //noinspection AssignmentToStaticFieldFromInstanceMethod
    staticDisposeTrace = disposeTrace;
  }

  public void refreshUi(boolean mayCheckReused, boolean onExplicitAction) {
    assert !myUpdating;
    LookupElement prevItem = getCurrentItem();
    myUpdating = true;
    try {
      final boolean reused = mayCheckReused && checkReused();
      boolean selectionVisible = isSelectionVisible();
      boolean itemsChanged = updateList(onExplicitAction, reused);
      if (isVisible()) {
        LOG.assertTrue(!ApplicationManager.getApplication().isUnitTestMode());
        myUi.refreshUi(selectionVisible, itemsChanged, reused, onExplicitAction);
      }
    } finally {
      myUpdating = false;
      fireCurrentItemChanged(prevItem, getCurrentItem());
    }
  }

  public void markReused() {
    synchronized (myList) {
      myArranger = myArranger.createEmptyCopy();
    }
    requestResize();
  }

  public void addAdvertisement(@NotNull final String text, final @Nullable Color bgColor) {
    if (containsDummyIdentifier(text)) {
      return;
    }

    myAdComponent.addAdvertisement(text, bgColor);
    requestResize();
  }

  public boolean isLookupDisposed() {
    return myDisposed;
  }

  public void checkValid() {
    if (myDisposed) {
      throw new AssertionError("Disposed at: " + disposeTrace);
    }
  }

  @Override
  public void showItemPopup(JBPopup hint) {
    final Rectangle bounds = getCurrentItemBounds();
    hint.show(new RelativePoint(getComponent(), new Point(bounds.x + bounds.width, bounds.y)));
  }

  @Override
  public boolean showElementActions() {
    if (!isVisible()) return false;

    final LookupElement element = getCurrentItem();
    if (element == null) {
      return false;
    }

    final Collection<LookupElementAction> actions = getActionsFor(element);
    if (actions.isEmpty()) {
      return false;
    }

    showItemPopup(
        JBPopupFactory.getInstance()
            .createListPopup(new LookupActionsStep(actions, this, element)));
    return true;
  }

  public Map<LookupElement, StringBuilder> getRelevanceStrings() {
    synchronized (myList) {
      return myPresentableArranger.getRelevanceStrings();
    }
  }

  public enum FocusDegree {
    FOCUSED,
    SEMI_FOCUSED,
    UNFOCUSED
  }
}
 private static class PackageCache {
   final ConcurrentMap<Pair<String, GlobalSearchScope>, PsiJavaPackage> packageInScopeCache =
       ContainerUtil.newConcurrentMap();
   final ConcurrentMap<String, Boolean> hasPackageInAllScopeCache =
       ContainerUtil.newConcurrentMap();
 }
Example #21
0
public class RefCountHolder {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.codeInsight.daemon.impl.RefCountHolder");

  private final PsiFile myFile;
  private final BidirectionalMap<PsiReference, PsiElement> myLocalRefsMap =
      new BidirectionalMap<PsiReference, PsiElement>();

  private final Map<PsiAnchor, Boolean> myDclsUsedMap = ContainerUtil.newConcurrentMap();
  private final Map<PsiReference, PsiImportStatementBase> myImportStatements =
      ContainerUtil.newConcurrentMap();
  private final AtomicReference<ProgressIndicator> myState =
      new AtomicReference<ProgressIndicator>(VIRGIN);
  private static final ProgressIndicator VIRGIN =
      new DaemonProgressIndicator(); // just created or cleared
  private static final ProgressIndicator READY = new DaemonProgressIndicator();
  private volatile ProgressIndicator analyzedUnder;

  private static class HolderReference extends SoftReference<RefCountHolder> {
    // Map holding hard references to RefCountHolder for each highlighting pass (identified by its
    // progress indicator)
    // there can be multiple passes running simultaneously (one actual and several passes just
    // canceled and winding down but still alive)
    // so there is a chance they overlap the usage of RCH
    // As soon as everybody finished using RCH, map become empty and the RefCountHolder is eligible
    // for gc
    private final Map<ProgressIndicator, RefCountHolder> map =
        new ConcurrentHashMap<ProgressIndicator, RefCountHolder>();

    public HolderReference(@NotNull RefCountHolder holder) {
      super(holder);
    }

    private void acquire(@NotNull ProgressIndicator indicator) {
      RefCountHolder holder = get();
      assert holder != null : "no way";
      map.put(indicator, holder);
      holder = get();
      assert holder != null : "can't be!";
    }

    private RefCountHolder release(@NotNull ProgressIndicator indicator) {
      return map.remove(indicator);
    }
  }

  private static final Key<HolderReference> REF_COUNT_HOLDER_IN_FILE_KEY =
      Key.create("REF_COUNT_HOLDER_IN_FILE_KEY");

  private static RefCountHolder getInstance(
      @NotNull PsiFile file, @NotNull ProgressIndicator indicator, boolean acquire) {
    HolderReference ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY);
    RefCountHolder holder = com.intellij.reference.SoftReference.dereference(ref);
    if (holder == null && acquire) {
      holder = new RefCountHolder(file);
      HolderReference newRef = new HolderReference(holder);
      while (true) {
        boolean replaced =
            ((UserDataHolderEx) file).replace(REF_COUNT_HOLDER_IN_FILE_KEY, ref, newRef);
        if (replaced) {
          ref = newRef;
          break;
        }
        ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY);
        RefCountHolder newHolder = com.intellij.reference.SoftReference.dereference(ref);
        if (newHolder != null) {
          holder = newHolder;
          break;
        }
      }
    }
    if (ref != null) {
      if (acquire) {
        ref.acquire(indicator);
      } else {
        ref.release(indicator);
      }
    }
    return holder;
  }

  @NotNull
  public static RefCountHolder startUsing(
      @NotNull PsiFile file, @NotNull ProgressIndicator indicator) {
    return getInstance(file, indicator, true);
  }

  @Nullable("might be gced")
  public static RefCountHolder endUsing(
      @NotNull PsiFile file, @NotNull ProgressIndicator indicator) {
    return getInstance(file, indicator, false);
  }

  private RefCountHolder(@NotNull PsiFile file) {
    myFile = file;
    log("c: created: ", myState.get(), " for ", file);
  }

  private void clear() {
    synchronized (myLocalRefsMap) {
      myLocalRefsMap.clear();
    }
    myImportStatements.clear();
    myDclsUsedMap.clear();
  }

  public void registerLocallyReferenced(@NotNull PsiNamedElement result) {
    myDclsUsedMap.put(PsiAnchor.create(result), Boolean.TRUE);
  }

  public void registerReference(
      @NotNull PsiJavaReference ref, @NotNull JavaResolveResult resolveResult) {
    PsiElement refElement = resolveResult.getElement();
    PsiFile psiFile = refElement == null ? null : refElement.getContainingFile();
    if (psiFile != null)
      psiFile =
          (PsiFile)
              psiFile
                  .getNavigationElement(); // look at navigation elements because all references
                                           // resolve into Cls elements when highlighting library
                                           // source
    if (refElement != null
        && psiFile != null
        && myFile.getViewProvider().equals(psiFile.getViewProvider())) {
      registerLocalRef(ref, refElement.getNavigationElement());
    }

    PsiElement resolveScope = resolveResult.getCurrentFileResolveScope();
    if (resolveScope instanceof PsiImportStatementBase) {
      registerImportStatement(ref, (PsiImportStatementBase) resolveScope);
    }
  }

  private void registerImportStatement(
      @NotNull PsiReference ref, @NotNull PsiImportStatementBase importStatement) {
    myImportStatements.put(ref, importStatement);
  }

  public boolean isRedundant(@NotNull PsiImportStatementBase importStatement) {
    return !myImportStatements.containsValue(importStatement);
  }

  private void registerLocalRef(@NotNull PsiReference ref, PsiElement refElement) {
    if (refElement instanceof PsiMethod
        && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true))
      return; // filter self-recursive calls
    if (refElement instanceof PsiClass
        && PsiTreeUtil.isAncestor(refElement, ref.getElement(), true))
      return; // filter inner use of itself
    synchronized (myLocalRefsMap) {
      myLocalRefsMap.put(ref, refElement);
    }
  }

  private void removeInvalidRefs() {
    synchronized (myLocalRefsMap) {
      for (Iterator<PsiReference> iterator = myLocalRefsMap.keySet().iterator();
          iterator.hasNext(); ) {
        PsiReference ref = iterator.next();
        if (!ref.getElement().isValid()) {
          PsiElement value = myLocalRefsMap.get(ref);
          iterator.remove();
          List<PsiReference> array = myLocalRefsMap.getKeysByValue(value);
          LOG.assertTrue(array != null);
          array.remove(ref);
        }
      }
    }
    for (Iterator<PsiReference> iterator = myImportStatements.keySet().iterator();
        iterator.hasNext(); ) {
      PsiReference ref = iterator.next();
      if (!ref.getElement().isValid()) {
        iterator.remove();
      }
    }
    removeInvalidFrom(myDclsUsedMap.keySet());
  }

  private static void removeInvalidFrom(@NotNull Collection<? extends PsiAnchor> collection) {
    for (Iterator<? extends PsiAnchor> it = collection.iterator(); it.hasNext(); ) {
      PsiAnchor element = it.next();
      if (element.retrieve() == null) it.remove();
    }
  }

  public boolean isReferenced(@NotNull PsiNamedElement element) {
    List<PsiReference> array;
    synchronized (myLocalRefsMap) {
      array = myLocalRefsMap.getKeysByValue(element);
    }
    if (array != null && !array.isEmpty() && !isParameterUsedRecursively(element, array))
      return true;

    Boolean usedStatus = myDclsUsedMap.get(PsiAnchor.create(element));
    return usedStatus == Boolean.TRUE;
  }

  public boolean isReferencedByMethodReference(
      @NotNull PsiMethod method, @NotNull LanguageLevel languageLevel) {
    if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) return false;

    List<PsiReference> array;
    synchronized (myLocalRefsMap) {
      array = myLocalRefsMap.getKeysByValue(method);
    }

    if (array != null && !array.isEmpty()) {
      for (PsiReference reference : array) {
        final PsiElement element = reference.getElement();
        if (element != null && element instanceof PsiMethodReferenceExpression) {
          return true;
        }
      }
    }

    return false;
  }

  private static boolean isParameterUsedRecursively(
      @NotNull PsiElement element, @NotNull List<PsiReference> array) {
    if (!(element instanceof PsiParameter)) return false;
    PsiParameter parameter = (PsiParameter) element;
    PsiElement scope = parameter.getDeclarationScope();
    if (!(scope instanceof PsiMethod)) return false;
    PsiMethod method = (PsiMethod) scope;
    int paramIndex = ArrayUtilRt.find(method.getParameterList().getParameters(), parameter);

    for (PsiReference reference : array) {
      if (!(reference instanceof PsiElement)) return false;
      PsiElement argument = (PsiElement) reference;

      PsiMethodCallExpression methodCallExpression =
          (PsiMethodCallExpression)
              new PsiMatcherImpl(argument)
                  .dot(PsiMatchers.hasClass(PsiReferenceExpression.class))
                  .parent(PsiMatchers.hasClass(PsiExpressionList.class))
                  .parent(PsiMatchers.hasClass(PsiMethodCallExpression.class))
                  .getElement();
      if (methodCallExpression == null) return false;
      PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
      if (method != methodExpression.resolve()) return false;
      PsiExpressionList argumentList = methodCallExpression.getArgumentList();
      PsiExpression[] arguments = argumentList.getExpressions();
      int argumentIndex = ArrayUtilRt.find(arguments, argument);
      if (paramIndex != argumentIndex) return false;
    }

    return true;
  }

  public boolean isReferencedForRead(@NotNull PsiVariable variable) {
    List<PsiReference> array;
    synchronized (myLocalRefsMap) {
      array = myLocalRefsMap.getKeysByValue(variable);
    }
    if (array == null) return false;
    for (PsiReference ref : array) {
      PsiElement refElement = ref.getElement();
      if (!(refElement instanceof PsiExpression)) { // possible with incomplete code
        return true;
      }
      if (PsiUtil.isAccessedForReading((PsiExpression) refElement)) {
        if (refElement.getParent() instanceof PsiExpression
            && refElement.getParent().getParent() instanceof PsiExpressionStatement
            && PsiUtil.isAccessedForWriting((PsiExpression) refElement)) {
          continue; // "var++;"
        }
        return true;
      }
    }
    return false;
  }

  public boolean isReferencedForWrite(@NotNull PsiVariable variable) {
    List<PsiReference> array;
    synchronized (myLocalRefsMap) {
      array = myLocalRefsMap.getKeysByValue(variable);
    }
    if (array == null) return false;
    for (PsiReference ref : array) {
      final PsiElement refElement = ref.getElement();
      if (!(refElement instanceof PsiExpression)) { // possible with incomplete code
        return true;
      }
      if (PsiUtil.isAccessedForWriting((PsiExpression) refElement)) {
        return true;
      }
    }
    return false;
  }

  public boolean analyze(
      @NotNull PsiFile file,
      TextRange dirtyScope,
      @NotNull Runnable analyze,
      @NotNull ProgressIndicator indicator) {
    ProgressIndicator old = myState.get();
    if (old != VIRGIN && old != READY) return false;
    if (!myState.compareAndSet(old, indicator)) {
      log("a: failed to change ", old, "->", indicator);
      return false;
    }
    log("a: changed ", old, "->", indicator);
    analyzedUnder = null;
    boolean completed = false;
    try {
      if (dirtyScope != null) {
        if (dirtyScope.equals(file.getTextRange())) {
          clear();
        } else {
          removeInvalidRefs();
        }
      }

      analyze.run();
      analyzedUnder = indicator;
      completed = true;
    } finally {
      ProgressIndicator resultState = completed ? READY : VIRGIN;
      boolean set = myState.compareAndSet(indicator, resultState);
      assert set : myState.get();
      log("a: changed after analyze", indicator, "->", resultState);
    }
    return true;
  }

  private static void log(@NonNls Object... s) {
    // System.err.println("RFC: "+ Arrays.asList(s));
  }

  public boolean retrieveUnusedReferencesInfo(
      @NotNull ProgressIndicator indicator, @NotNull Runnable analyze) {
    ProgressIndicator old = myState.get();
    if (!myState.compareAndSet(READY, indicator)) {
      log("r: failed to change ", old, "->", indicator);
      return false;
    }
    log("r: changed ", old, "->", indicator);
    try {
      if (analyzedUnder != indicator) {
        return false;
      }
      analyze.run();
    } finally {
      boolean set = myState.compareAndSet(indicator, READY);
      assert set : myState.get();
      log("r: changed back ", indicator, "->", READY);
    }
    return true;
  }
}
public class GitUserRegistry implements Disposable, VcsListener {

  private static final Logger LOG = Logger.getInstance(GitUserRegistry.class);

  @NotNull private final Project myProject;
  @NotNull private final ProjectLevelVcsManager myVcsManager;
  @NotNull private final VcsLogObjectsFactory myFactory;
  @NotNull private final Map<VirtualFile, VcsUser> myUserMap = ContainerUtil.newConcurrentMap();

  public GitUserRegistry(
      @NotNull Project project,
      @NotNull ProjectLevelVcsManager vcsManager,
      @NotNull VcsLogObjectsFactory factory) {
    myProject = project;
    myVcsManager = vcsManager;
    myFactory = factory;
  }

  public void activate() {
    myProject
        .getMessageBus()
        .connect()
        .subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, this);
    directoryMappingChanged();
  }

  @Nullable
  public VcsUser getUser(@NotNull VirtualFile root) {
    return myUserMap.get(root);
  }

  @Nullable
  public VcsUser getOrReadUser(@NotNull VirtualFile root) {
    VcsUser user = myUserMap.get(root);
    if (user == null) {
      try {
        user = readCurrentUser(myProject, root);
        if (user != null) {
          myUserMap.put(root, user);
        }
      } catch (VcsException e) {
        LOG.warn("Could not retrieve user name in " + root, e);
      }
    }
    return user;
  }

  @Nullable
  private VcsUser readCurrentUser(@NotNull Project project, @NotNull VirtualFile root)
      throws VcsException {
    String userName = GitConfigUtil.getValue(project, root, GitConfigUtil.USER_NAME);
    String userEmail =
        StringUtil.notNullize(GitConfigUtil.getValue(project, root, GitConfigUtil.USER_EMAIL));
    return userName == null ? null : myFactory.createUser(userName, userEmail);
  }

  @Override
  public void dispose() {
    myUserMap.clear();
  }

  @Override
  public void directoryMappingChanged() {
    GitVcs vcs = GitVcs.getInstance(myProject);
    if (vcs == null) {
      return;
    }
    final VirtualFile[] roots = myVcsManager.getRootsUnderVcs(vcs);
    final Collection<VirtualFile> rootsToCheck =
        ContainerUtil.filter(
            roots,
            new Condition<VirtualFile>() {
              @Override
              public boolean value(VirtualFile root) {
                return getUser(root) == null;
              }
            });
    if (!rootsToCheck.isEmpty()) {
      ApplicationManager.getApplication()
          .executeOnPooledThread(
              new Runnable() {
                public void run() {
                  for (VirtualFile root : rootsToCheck) {
                    getOrReadUser(root);
                  }
                }
              });
    }
  }
}