/*
* Copyright 2003-2011 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 jetbrains.mps.typesystem.inference;
import jetbrains.mps.classloading.ClassLoaderManager;
import jetbrains.mps.components.CoreComponent;
import jetbrains.mps.newTypesystem.context.CachingTypecheckingContext;
import jetbrains.mps.newTypesystem.context.HoleTypecheckingContext;
import jetbrains.mps.newTypesystem.context.IncrementalTypecheckingContext;
import jetbrains.mps.newTypesystem.context.InferenceTypecheckingContext;
import jetbrains.mps.newTypesystem.context.TargetTypecheckingContext;
import jetbrains.mps.newTypesystem.context.TargetTypecheckingContext_Tracer;
import jetbrains.mps.newTypesystem.context.TracingTypecheckingContext;
import jetbrains.mps.typesystem.inference.ITypechecking.Action;
import jetbrains.mps.typesystem.inference.ITypechecking.Computation;
import jetbrains.mps.typesystem.inference.util.SubtypingCache;
import jetbrains.mps.util.Computable;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class TypeContextManager implements CoreComponent {
private static final Logger LOG = LogManager.getLogger(TypeContextManager.class);
private static TypeContextManager INSTANCE;
private static boolean myReported = false;
private final Object myLock = new Object();
private Map<ITypeContextOwner, TypecheckingContextHolder> myTypeCheckingContexts = Collections.synchronizedMap(new HashMap<ITypeContextOwner, TypecheckingContextHolder>());
private TypeChecker myTypeChecker;
private AtomicBoolean myDisposeRequested = new AtomicBoolean(false);
private AtomicInteger myExecuting = new AtomicInteger(0);
//TypeContextManager is a singleton, so we can omit remove() here though the field is not static
private ThreadLocal<ITypeContextOwner> myTypecheckingContextOwner = new ThreadLocal<ITypeContextOwner>() {
@Override
protected ITypeContextOwner initialValue() {
return new DefaultTypecheckingContextOwner() {
@Override
public boolean reuseTypecheckingContext() {
return false;
}
};
}
};
private ThreadLocal<SubtypingCache> mySubtypingCache = new ThreadLocal<SubtypingCache>();
public static TypeContextManager getInstance() {
return INSTANCE;
}
public TypeContextManager(TypeChecker typeChecker, ClassLoaderManager classLoaderManager) {
myTypeChecker = typeChecker;
}
@Override
public void init() {
if (INSTANCE != null) {
throw new IllegalStateException("double initialization");
}
INSTANCE = this;
}
@Override
public void dispose() {
// signal to the Executor
if (!myDisposeRequested.compareAndSet(false, true)) return;
// if busy, bail
if (myExecuting.get() > 0) return;
doDispose();
}
private void doDispose() {
for (Map.Entry<ITypeContextOwner, TypecheckingContextHolder> entry: myTypeCheckingContexts.entrySet()) {
entry.getValue().dispose();
}
myTypeCheckingContexts.clear();
INSTANCE = null;
}
public void runTypeCheckingAction(SNode node, ITypechecking.Action r) {
new Executor<Object>(node, r).execute();
}
public void runTypeCheckingAction(@NotNull final ITypeContextOwner contextOwner, SNode node, ITypechecking.Action r) {
new Executor<Object>(contextOwner, node, r).execute();
}
public <T> T runTypeCheckingComputation(@NotNull final ITypeContextOwner contextOwner, SNode node, Computation<T> r) {
return new Executor<T>(contextOwner, node, r).execute();
}
public void runResolveAction(Runnable r) {
runTypecheckingAction(new NonReusableTypecheckingContextOwner(), r);
}
public <T> T runResolveAction(Computable<T> computable) {
return runTypecheckingAction(new NonReusableTypecheckingContextOwner(), computable);
}
public void runTypecheckingAction(ITypeContextOwner contextOwner, Runnable r) {
new Executor<Object>(contextOwner, r).execute();
}
public <T> T runTypecheckingAction(ITypeContextOwner contextOwner, Computable<T> computable) {
return new Executor<T>(contextOwner, computable).execute();
}
public TypeCheckingContext createTypeCheckingContext(SNode node) {
if (myTypeChecker.isGenerationMode()) {
return new TargetTypecheckingContext(node, myTypeChecker);
} else {
return new IncrementalTypecheckingContext(node, myTypeChecker);
}
}
public HoleTypecheckingContext createHoleTypecheckingContext(SNode node) {
return new HoleTypecheckingContext(node, myTypeChecker);
}
public TypeCheckingContext createInferenceTypeCheckingContext(SNode node) {
return new InferenceTypecheckingContext(node, myTypeChecker);
}
public TypeCheckingContext createTracingTypeCheckingContext(SNode node) {
return new TracingTypecheckingContext(node, myTypeChecker);
}
public TypeCheckingContext acquireTypecheckingContext(SNode node, ITypeContextOwner contextOwner) {
return getOrCreateContext(node, contextOwner);
}
@Deprecated
public void releaseTypecheckingContext(SNode node, ITypeContextOwner contextOwner) {
releaseTypecheckingContext(contextOwner);
}
public void releaseTypecheckingContext(ITypeContextOwner contextOwner) {
//if node is disposed, then context was removed by beforeModelDisposed/beforeRootDeleted listener
synchronized (myLock) {
TypecheckingContextHolder contextHolder = myTypeCheckingContexts.get(contextOwner);
if (contextHolder == null) return;
ITypeContextOwner o = contextHolder.getOwner();
if (o == contextOwner) {
contextHolder.release();
if (!contextHolder.isActive()) {
myTypeCheckingContexts.remove(contextOwner);
}
}
}
}
private class Executor<T> {
private ITypeContextOwner myContextOwner;
private SNode myContextNode;
private Action myAction;
private Computable<T> myComputable;
private Computation<T> myComputation;
private Runnable myRunnable;
Executor(ITypechecking.Action action) {
myAction = action;
}
Executor(SNode contextNode, ITypechecking.Action action) {
myContextNode = contextNode;
myAction = action;
}
Executor(ITypeContextOwner contextOwner, SNode contextNode, ITypechecking.Action action) {
myContextOwner = contextOwner;
myContextNode = contextNode;
myAction = action;
}
Executor(ITypeContextOwner contextOwner, SNode contextNode, Computation<T> computation) {
myContextOwner = contextOwner;
myContextNode = contextNode;
myComputation = computation;
}
Executor(ITypeContextOwner contextOwner, Computable<T> computable) {
myContextOwner = contextOwner;
myComputable = computable;
}
Executor(ITypeContextOwner contextOwner, Runnable runnable) {
myContextOwner = contextOwner;
myRunnable = runnable;
}
T execute() {
// one more task executing
myExecuting.incrementAndGet();
// dispose has been called? no-no-no
if (myDisposeRequested.get()) {
if (myExecuting.decrementAndGet() == 0) {
doDispose();
}
return null;
}
final ITypeContextOwner savedOwner = myTypecheckingContextOwner.get();
if (myContextOwner != null) {
myTypecheckingContextOwner.set(myContextOwner);
}
final ITypeContextOwner contextOwner = myTypecheckingContextOwner.get();
TypeCheckingContext context = null;
if (myContextNode != null) {
context = acquireTypecheckingContext(myContextNode, contextOwner);
}
final SubtypingCache savedSubtypingCache = mySubtypingCache.get();
mySubtypingCache.set(null);
try {
return doExecute(context);
}
finally {
mySubtypingCache.set(savedSubtypingCache);
if (context != null) {
releaseTypecheckingContext(contextOwner);
}
if (myContextOwner != null) {
myTypecheckingContextOwner.set(savedOwner);
}
// do dispose on last task finished
int executingTasks = myExecuting.decrementAndGet();
if (myDisposeRequested.get() && executingTasks == 0) {
doDispose();
}
}
}
private T doExecute(TypeCheckingContext context) {
if (myAction != null) {
myAction.run(context);
return null;
}
else if (myComputation != null) {
return myComputation.compute(context);
}
else if (myComputable != null) {
return myComputable.compute();
}
else if (myRunnable != null) {
myRunnable.run();
return null;
}
throw new IllegalStateException();
}
}
private TypeCheckingContext getOrCreateContext(SNode node, ITypeContextOwner owner) {
if (node == null) return null;
final SModel model = node.getModel();
assert model != null;
synchronized (myLock) {
final TypecheckingContextHolder contextHolder = myTypeCheckingContexts.get(owner);
if (contextHolder != null) {
// reuse the typechecking context
return contextHolder.acquire(node);
}
else {
// not found, create new
if (!owner.reuseTypecheckingContext()) {
final NonReusableTypecheckingContextHolder newContextHolder = new NonReusableTypecheckingContextHolder(owner);
myTypeCheckingContexts.put(owner, newContextHolder);
return newContextHolder.acquire(node);
}
else {
final CountingTypecheckingContextHolder newContextHolder = new CountingTypecheckingContextHolder(owner);
myTypeCheckingContexts.put(owner, newContextHolder);
return newContextHolder.acquire(node);
}
}
}
}
public TypeCheckingContext createTypeCheckingContextForResolve(SNode node) {
SNode root = node.getContainingRoot();
return new CachingTypecheckingContext(root, myTypeChecker);
}
/*package*/ SubtypingCache getSubtypingCache() {
final SubtypingCache subtypingCache = mySubtypingCache.get();
if (subtypingCache != null) return subtypingCache;
final ITypeContextOwner typeContextOwner = myTypecheckingContextOwner.get();
final SubtypingCache newSubtypingCache = typeContextOwner.createSubtypingCache();
if (newSubtypingCache != null) {
mySubtypingCache.set(newSubtypingCache);
}
return newSubtypingCache;
}
@Nullable
/*package*/ SNode getTypeOf(final SNode node) {
if (node == null) return null;
if (myTypeChecker.isGenerationMode()) {
TypeCheckingContext context = myTypeChecker.hasPerformanceTracer() ?
new TargetTypecheckingContext_Tracer(node, myTypeChecker) :
new TargetTypecheckingContext(node, myTypeChecker);
try {
return context.getTypeOf_generationMode(node);
} finally {
context.dispose();
}
}
//now we are not in generation mode
final ITypeContextOwner contextOwner = myTypecheckingContextOwner.get();
return new Executor<SNode>(contextOwner, node, new Computation<SNode>() {
@Override
public SNode compute(TypeCheckingContext context) {
return context != null ? context.getTypeOf(node, myTypeChecker) : null;
}
}).execute();
}
private interface TypecheckingContextHolder {
ITypeContextOwner getOwner();
void clear();
void dispose();
TypeCheckingContext acquire(SNode node);
void release();
boolean isActive();
}
private class CountingTypecheckingContextHolder implements TypecheckingContextHolder {
private final ITypeContextOwner myOwner;
private AtomicReference<TypeCheckingContext> myContext = new AtomicReference<TypeCheckingContext>(null);
private Map<Thread, Integer> myCounters = Collections.synchronizedMap(new HashMap<Thread, Integer>());
CountingTypecheckingContextHolder(ITypeContextOwner owner) {
myOwner = owner;
}
@Override
public ITypeContextOwner getOwner() {
return myOwner;
}
@Override
public void clear() {
if (myContext.get() != null) {
myContext.get().clear();
}
}
@Override
public void dispose() {
if (myContext.get() != null) {
myContext.get().dispose();
}
myContext.set(null);
myCounters.clear();
}
@Override
public void release() {
Integer ctr = myCounters.get(Thread.currentThread());
if (ctr == null) return;
if ((ctr -= 1) == 0) {
myCounters.remove(Thread.currentThread());
if (myCounters.isEmpty()) {
myContext.get().dispose();
myContext.set(null);
}
}
else {
myCounters.put(Thread.currentThread(), ctr);
}
}
@Override
public TypeCheckingContext acquire(SNode node) {
if (myContext.get() == null) {
myContext.set(myOwner.createTypecheckingContext(node, TypeContextManager.this));
myCounters.put(Thread.currentThread(), 1);
}
else {
Integer ctr = myCounters.get(Thread.currentThread());
myCounters.put(Thread.currentThread(), (ctr != null ? ctr : 0) + 1);
}
return myContext.get();
}
@Override
public boolean isActive() {
return !myCounters.isEmpty();
}
}
private class NonReusableTypecheckingContextHolder implements TypecheckingContextHolder {
private LinkedList<TypeCheckingContext> myContexts = new LinkedList<TypeCheckingContext>();
private ITypeContextOwner myOwner;
NonReusableTypecheckingContextHolder(ITypeContextOwner owner) {
myOwner = owner;
}
@Override
public ITypeContextOwner getOwner() {
return myOwner;
}
@Override
public void clear() {
for (TypeCheckingContext context : myContexts) {
context.clear();
}
}
@Override
public void dispose() {
for (TypeCheckingContext context : myContexts) {
context.dispose();
}
myContexts.clear();
}
@Override
public TypeCheckingContext acquire(SNode node) {
if (myContexts.size() >= 10) {
LOG.warn("too many non-reusable typechecking contexts");
return null;
}
for (TypeCheckingContext ctx : myContexts) {
if (ctx.getNode() == node) {
LOG.warn("double typechecking context acquiring");
return null;
}
}
final TypeCheckingContext ctx = myOwner.createTypecheckingContext(node, TypeContextManager.this);
myContexts.add(ctx);
return ctx;
}
@Override
public void release() {
if (!myContexts.isEmpty()) {
final TypeCheckingContext ctx = myContexts.removeLast();
ctx.dispose();
}
}
@Override
public boolean isActive() {
return !myContexts.isEmpty();
}
}
}