com.intellij.openapi.util.RecursionManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of util Show documentation
Show all versions of util Show documentation
A packaging of the IntelliJ Community Edition util library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* Copyright 2000-2013 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.openapi.util;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.containers.SoftHashMap;
import com.intellij.util.containers.SoftKeySoftValueHashMap;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
/**
* There are moments when a computation A requires the result of computation B, which in turn requires C, which (unexpectedly) requires A.
* If there are no other ways to solve it, it helps to track all the computations in the thread stack and return some default value when
* asked to compute A for the second time. {@link RecursionGuard#doPreventingRecursion(Object, Computable)} does precisely this.
*
* It's quite useful to cache some computation results to avoid performance problems. But not everyone realises that in the above situation it's
* incorrect to cache the results of B and C, because they all are based on the default incomplete result of the A calculation. If the actual
* computation sequence were C->A->B->C, the result of the outer C most probably wouldn't be the same as in A->B->C->A, where it depends on
* the null A result directly. The natural wish is that the program with cache enabled has the same results as the one without cache. In the above
* situation the result of C would depend on the order of invocations of C and A, which can be hardly predictable in multi-threaded environments.
*
* Therefore if you use any kind of cache, it probably would make your program safer to cache only when it's safe to do this. See
* {@link com.intellij.openapi.util.RecursionGuard#markStack()} and {@link com.intellij.openapi.util.RecursionGuard.StackStamp#mayCacheNow()}
* for the advice.
*
* @see RecursionGuard
* @see RecursionGuard.StackStamp
* @author peter
*/
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
public class RecursionManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.RecursionManager");
private static final Object NULL = new Object();
private static final ThreadLocal ourStack = new ThreadLocal() {
@Override
protected CalculationStack initialValue() {
return new CalculationStack();
}
};
private static boolean ourAssertOnPrevention;
/**
* @see RecursionGuard#doPreventingRecursion(Object, boolean, Computable)
*/
@SuppressWarnings("JavaDoc")
@Nullable
public static T doPreventingRecursion(@NotNull Object key, boolean memoize, Computable computation) {
return createGuard(computation.getClass().getName()).doPreventingRecursion(key, memoize, computation);
}
/**
* @param id just some string to separate different recursion prevention policies from each other
* @return a helper object which allow you to perform reentrancy-safe computations and check whether caching will be safe.
*/
public static RecursionGuard createGuard(@NonNls final String id) {
return new RecursionGuard() {
@Override
public T doPreventingRecursion(@NotNull Object key, boolean memoize, @NotNull Computable computation) {
MyKey realKey = new MyKey(id, key, true);
final CalculationStack stack = ourStack.get();
if (stack.checkReentrancy(realKey)) {
if (ourAssertOnPrevention) {
throw new AssertionError("Endless recursion prevention occurred");
}
return null;
}
if (memoize) {
Object o = stack.getMemoizedValue(realKey);
if (o != null) {
SoftKeySoftValueHashMap map = stack.intermediateCache.get(realKey);
if (map != null) {
for (MyKey noCacheUntil : map.keySet()) {
stack.prohibitResultCaching(noCacheUntil);
}
}
//noinspection unchecked
return o == NULL ? null : (T)o;
}
}
realKey = new MyKey(id, key, false);
final int sizeBefore = stack.progressMap.size();
stack.beforeComputation(realKey);
final int sizeAfter = stack.progressMap.size();
int startStamp = stack.memoizationStamp;
try {
T result = computation.compute();
if (memoize) {
stack.maybeMemoize(realKey, result == null ? NULL : result, startStamp);
}
return result;
}
finally {
try {
stack.afterComputation(realKey, sizeBefore, sizeAfter);
}
catch (Throwable e) {
//noinspection ThrowFromFinallyBlock
throw new RuntimeException("Throwable in afterComputation", e);
}
stack.checkDepth("4");
}
}
@NotNull
@Override
public StackStamp markStack() {
final int stamp = ourStack.get().reentrancyCount;
return new StackStamp() {
@Override
public boolean mayCacheNow() {
return stamp == ourStack.get().reentrancyCount;
}
};
}
@NotNull
@Override
public List