// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.xml.impl;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.xml.DomReflectionUtil;
import com.intellij.util.xml.Implementation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * @author peter
 */
class ImplementationClassCache {
  private static final Comparator<Class> CLASS_COMPARATOR = (o1, o2) -> {
    if (o1.isAssignableFrom(o2)) return 1;
    if (o2.isAssignableFrom(o1)) return -1;
    if (o1.equals(o2)) return 0;
    throw new AssertionError("Incompatible implementation classes: " + o1 + " & " + o2);
  };


  private final MultiMap<String, DomImplementationClassEP> myImplementationClasses = new MultiMap<>();
  private final SofterCache<Class<?>, Class<?>> myCache = SofterCache.create(dom -> calcImplementationClass(dom));

  ImplementationClassCache(ExtensionPointName<DomImplementationClassEP> epName) {
    Application app = ApplicationManager.getApplication();
    if (!app.isDisposed()) {
      epName.getPoint(null).addExtensionPointListener(new ExtensionPointListener<DomImplementationClassEP>() {
        @Override
        public void extensionAdded(@NotNull DomImplementationClassEP ep, @NotNull PluginDescriptor pluginDescriptor) {
          myImplementationClasses.putValue(ep.interfaceName, ep);
          clearCache();
        }

        @Override
        public void extensionRemoved(@NotNull DomImplementationClassEP ep, @NotNull PluginDescriptor pluginDescriptor) {
          myImplementationClasses.remove(ep.interfaceName, ep);
          clearCache();
        }
      }, true, app);
    }
  }

  private Class<?> calcImplementationClass(Class<?> concreteInterface) {
    final TreeSet<Class> set = new TreeSet<>(CLASS_COMPARATOR);
    findImplementationClassDFS(concreteInterface, set);
    if (!set.isEmpty()) {
      return set.first();
    }
    final Implementation implementation = DomReflectionUtil.findAnnotationDFS(concreteInterface, Implementation.class);
    return implementation == null ? concreteInterface : implementation.value();
  }

  private void findImplementationClassDFS(final Class concreteInterface, SortedSet<? super Class> results) {
    final Collection<DomImplementationClassEP> values = myImplementationClasses.get(concreteInterface.getName());
    for (DomImplementationClassEP value : values) {
      if (value.getInterfaceClass() == concreteInterface) {
        results.add(value.getImplementationClass());
        return;
      }
    }
    for (Class<?> aClass1 : concreteInterface.getInterfaces()) {
      findImplementationClassDFS(aClass1, results);
    }
  }

  void registerImplementation(Class<?> domElementClass, Class<?> implementationClass, @Nullable Disposable parentDisposable) {
    final DomImplementationClassEP ep = new DomImplementationClassEP() {
      @Override
      public Class getInterfaceClass() {
        return domElementClass;
      }

      @Override
      public Class getImplementationClass() {
        return implementationClass;
      }
    };
    myImplementationClasses.putValue(domElementClass.getName(), ep);
    if (parentDisposable != null) {
      Disposer.register(parentDisposable, new Disposable() {
        @Override
        public void dispose() {
          myImplementationClasses.remove(domElementClass.getName());
        }
      });
    }
    myCache.clearCache();
  }

  Class get(Class<?> key) {
    Class impl = myCache.getCachedValue(key);
    return impl == key ? null : impl;
  }

  void clearCache() {
    myCache.clearCache();
  }
}

@@ -243,270 +243,37 @@
 api.
-application.Application;%0Aimport com.intellij.openapi.application.ApplicationManager;%0Aimport com.intellij.openapi.extensions.ExtensionPointListener;%0Aimport com.intellij.openapi.extensions.ExtensionPointName;%0Aimport com.intellij.openapi.extensions.PluginDescriptor
+extensions.ExtensionPointName
 ;%0Aim
@@ -456,50 +456,8 @@
 on;%0A
-import org.jetbrains.annotations.NotNull;%0A
 impo
@@ -1097,21 +1097,15 @@
 lass
-%3C?%3E
 , Class
-%3C?%3E
 %3E my
@@ -1260,336 +1260,68 @@
     
-Application app = ApplicationManager.getApplication();%0A    if (!app.isDisposed()) %7B%0A      epName.getPoint(null).addExtensionPointListener(new ExtensionPointListener%3CDomImplementationClassEP%3E() %7B%0A        @Override%0A        public void extensionAdded(@NotNull DomImplementationClassEP ep, @NotNull PluginDescriptor pluginDescriptor
+for (DomImplementationClassEP ep : epName.getExtensionList()
 ) %7B%0A
@@ -1318,36 +1318,32 @@
 List()) %7B%0A      
-   
- 
 myImplementation
@@ -1386,301 +1386,8 @@
 p);%0A
-          clearCache();%0A        %7D%0A%0A        @Override%0A        public void extensionRemoved(@NotNull DomImplementationClassEP ep, @NotNull PluginDescriptor pluginDescriptor) %7B%0A          myImplementationClasses.remove(ep.interfaceName, ep);%0A          clearCache();%0A        %7D%0A      %7D, true, app);%0A
     
@@ -1408,19 +1408,16 @@
 te Class
-%3C?%3E
  calcImp
@@ -1438,19 +1438,16 @@
 ss(Class
-%3C?%3E
  concret
@@ -2270,24 +2270,27 @@
    for (
+final 
 Class
-%3C?%3E
  aClass1
@@ -2390,24 +2390,37 @@
    %7D%0A  %7D%0A%0A  
+public final 
 void registe
@@ -2435,24 +2435,27 @@
 ntation(
+final 
 Class
-%3C?%3E
  domElem
@@ -2467,17 +2467,20 @@
 ass,
+ final
  Class
-%3C?%3E
  imp
@@ -2496,16 +2496,59 @@
 onClass,
+%0A                                          
  @Nullab
@@ -2550,16 +2550,22 @@
 ullable 
+final 
 Disposab
@@ -3215,16 +3215,23 @@
 %0A  %7D%0A%0A  
+public 
 Class ge
@@ -3241,11 +3241,8 @@
 lass
-%3C?%3E
  key
@@ -3338,58 +3338,7 @@
  %7D%0A%0A
-  void clearCache() %7B%0A    myCache.clearCache();%0A  %7D
 %0A%7D%0A

revert ImplementationClassCache