/*
 * Copyright 2000-2015 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 com.intellij.testFramework;

import com.intellij.ide.IdeEventQueue;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.impl.ProjectImpl;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.PairProcessor;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.io.PersistentEnumeratorBase;
import com.intellij.util.ref.DebugReflectionUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import javax.swing.*;
import java.util.*;

public class LeakHunter {
  @TestOnly
  public static void checkProjectLeak() throws Exception {
    checkLeak(allRoots(), ProjectImpl.class, project -> !project.isDefault() && !project.isLight());
  }

  @TestOnly
  public static void checkNonDefaultProjectLeak() throws Exception {
    checkLeak(allRoots(), ProjectImpl.class, project -> !project.isDefault());
  }

  @TestOnly
  public static void checkLeak(@NotNull Object root, @NotNull Class<?> suspectClass) throws AssertionError {
    checkLeak(root, suspectClass, null);
  }

  /**
   * Checks if there is a memory leak if an object of type {@code suspectClass} is strongly accessible via references from the {@code root} object.
   */
  @TestOnly
  public static <T> void checkLeak(@NotNull Collection<Object> roots,
                                   @NotNull Class<T> suspectClass,
                                   @Nullable final Condition<? super T> isReallyLeak) throws AssertionError {
    processLeaks(roots, suspectClass, isReallyLeak, (leaked, backLink)->{
      String place = leaked instanceof Project ? PlatformTestCase.getCreationPlace((Project)leaked) : "";
      System.out.println("Found leaked "+leaked.getClass() + ": "+leaked +
                         "; hash: " + System.identityHashCode(leaked) + "; place: " + place);
      System.out.println(backLink);
      System.out.println(";-----");

      throw new AssertionError();
    });
  }

  /**
   * Checks if there is a memory leak if an object of type {@code suspectClass} is strongly accessible via references from the {@code root} object.
   */
  @TestOnly
  static <T> void processLeaks(@NotNull Collection<Object> roots,
                               @NotNull Class<T> suspectClass,
                               @Nullable final Condition<? super T> isReallyLeak,
                               @NotNull final PairProcessor<? super T, Object> processor) throws AssertionError {
    if (SwingUtilities.isEventDispatchThread()) {
      UIUtil.dispatchAllInvocationEvents();
    }
    else {
      UIUtil.pump();
    }
    PersistentEnumeratorBase.clearCacheForTests();
    ApplicationManager.getApplication().runReadAction(() -> {
      DebugReflectionUtil.walkObjects(10000, roots, suspectClass, Conditions.alwaysTrue(), (value, backLink) -> {
        @SuppressWarnings("unchecked")
        T leaked = (T)value;
        if (isReallyLeak == null || isReallyLeak.value(leaked)) {
          return processor.process(leaked, backLink);
        }
        return true;
      });
    });
  }

  /**
   * Checks if there is a memory leak if an object of type {@code suspectClass} is strongly accessible via references from the {@code root} object.
   */
  @TestOnly
  public static <T> void checkLeak(@NotNull Object root, @NotNull Class<T> suspectClass, @Nullable final Condition<? super T> isReallyLeak) throws AssertionError {
    checkLeak(Collections.singletonList(root), suspectClass, isReallyLeak);
  }

  @NotNull
  public static List<Object> allRoots() {
    ClassLoader classLoader = LeakHunter.class.getClassLoader();
    // inspect static fields of all loaded classes
    Vector<Class> allLoadedClasses = ReflectionUtil.getField(classLoader.getClass(), classLoader, Vector.class, "classes");

    return Arrays.asList(ApplicationManager.getApplication(),
                         Disposer.getTree(),
                         IdeEventQueue.getInstance(),
                         LaterInvocator.getLaterInvocatorQueue(),
                         ThreadTracker.getThreads(),
                         allLoadedClasses);
  }
}

@@ -3520,24 +3520,64 @@
 ForTests();%0A
+    LaterInvocator.purgeExpiredItems();%0A
     Applicat

purge later invocator queue to try to fix mysterious android tests failures