/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.plugins.gradle.config;

import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.externalSystem.psi.search.ExternalModuleBuildGlobalSearchScope;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.impl.PackageDirectoryCache;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.NonClasspathClassFinder;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.search.EverythingGlobalScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.containers.ConcurrentFactoryMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.service.GradleBuildClasspathManager;
import org.jetbrains.plugins.groovy.GroovyFileType;

import java.util.List;
import java.util.Map;

/**
 * @author peter
 */
public class GradleClassFinder extends NonClasspathClassFinder {

  @NotNull private final GradleBuildClasspathManager myBuildClasspathManager;

  // Bug: IDEA-200807
  // The parent class can publish a reference to this object before the constructor has returned.
  // Thus, it's possible that not all fields of this object are initialized by the time they
  // are accessed in clearCache(). Workaround is to mark this field as volatile and do a
  // null check in clearCache().
  //
  // It is unknown if different threads can read stale or uninitialized values for the
  // myBuildClasspathManager instance variable at this moment.
  private volatile Map<String, PackageDirectoryCache> myCaches;

  public GradleClassFinder(@NotNull Project project, @NotNull GradleBuildClasspathManager buildClasspathManager) {
    super(project, JavaFileType.DEFAULT_EXTENSION, GroovyFileType.DEFAULT_EXTENSION);
    myBuildClasspathManager = buildClasspathManager;
    myCaches = ConcurrentFactoryMap.createMap(path -> createCache(myBuildClasspathManager.getModuleClasspathEntries(path)));
  }

  @Override
  protected List<VirtualFile> calcClassRoots() {
    return myBuildClasspathManager.getAllClasspathEntries();
  }

  @NotNull
  @Override
  protected PackageDirectoryCache getCache(@Nullable GlobalSearchScope scope) {
    if (scope instanceof ExternalModuleBuildGlobalSearchScope) {
      return myCaches.get(((ExternalModuleBuildGlobalSearchScope)scope).getExternalModulePath());
    }
    return super.getCache(scope);
  }

  @Override
  public void clearCache() {
    super.clearCache();
    if(myCaches != null) {
      myCaches.clear();
    }
  }

  @Override
  public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
    PsiClass aClass = super.findClass(qualifiedName, scope);
    if (aClass == null || scope instanceof ExternalModuleBuildGlobalSearchScope || scope instanceof EverythingGlobalScope) {
      return aClass;
    }

    PsiFile containingFile = aClass.getContainingFile();
    VirtualFile file = containingFile != null ? containingFile.getVirtualFile() : null;
    return (file != null &&
            !ProjectFileIndex.SERVICE.getInstance(myProject).isInContent(file) &&
            !ProjectFileIndex.SERVICE.getInstance(myProject).isInLibraryClasses(file) &&
            !ProjectFileIndex.SERVICE.getInstance(myProject).isInLibrarySource(file)) ? aClass : null;
  }

  @NotNull
  @Override
  public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
    if (scope instanceof ExternalModuleBuildGlobalSearchScope) {
      return super.getSubPackages(psiPackage, scope);
    }
    else {
      return PsiPackage.EMPTY_ARRAY;
    }
  }
}
@@ -1648,17 +1648,16 @@
 inder %7B%0A
-%0A
   @NotNu
@@ -1730,517 +1730,23 @@
 er;%0A
-%0A
   
-// Bug: IDEA-200807%0A  // The parent class can publish a reference to this object before the constructor has returned.%0A  // Thus, it's possible that not all fields of this object are initialized by the time they%0A  // are accessed in clearCache(). Workaround is to mark this field as volatile and do a%0A  // null check in clearCache().%0A  //%0A  // It is unknown if different threads can read stale or uninitialized values for the%0A  // myBuildClasspathManager instance variable at this moment.%0A  private volatile
+private final
  Map
@@ -2675,22 +2675,284 @@
 ache();%0A
+    // The parent class can publish a reference to this object before the constructor has returned.%0A    // Thus, it's possible that not all fields of this object are initialized by the time they%0A    // are accessed in clearCache(). Workaround is to null check.%0A
     if
+ 
 (myCache
@@ -3476,17 +3476,16 @@
  return 
-(
 file != 
@@ -3488,25 +3488,24 @@
  != null &&%0A
- 
            !
@@ -3565,33 +3565,32 @@
 ontent(file) &&%0A
- 
            !Proj
@@ -3664,33 +3664,32 @@
 ) &&%0A           
- 
 !ProjectFileInde
@@ -3744,17 +3744,16 @@
 ce(file)
-)
  ? aClas
@@ -3891,36 +3891,39 @@
 pe scope) %7B%0A    
-if (
+return 
 scope instanceof
@@ -3959,32 +3959,18 @@
 rchScope
-) %7B%0A      return
+ ?
  super.g
@@ -4005,39 +4005,10 @@
 ope)
-;%0A    %7D%0A    else %7B%0A      return
+ :
  Psi
@@ -4028,19 +4028,13 @@
 _ARRAY;%0A
-    %7D%0A
   %7D%0A%7D

make field final back to be more thread-safe, comment the relevant part