/*
* Copyright 2003-2017 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.smodel;
import jetbrains.mps.smodel.references.ImmatureReferences;
import jetbrains.mps.smodel.references.UnregisteredNodes;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.repository.WriteActionListener;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* The actual implementation of {@link org.jetbrains.mps.openapi.module.ModelAccess} interface methods
* Probably it is better to merge it with
* {@link jetbrains.mps.project.ProjectModelAccess} and
* {@link jetbrains.mps.smodel.ModelAccessBase}
* which currently simply delegate all methods to this class
*
* @see org.jetbrains.mps.openapi.module.ModelAccess
*/
public abstract class ModelAccess implements ModelCommandProjectExecutor, org.jetbrains.mps.openapi.module.ModelAccess {
protected final WriteActionDispatcher myWriteActionDispatcher = new WriteActionDispatcher();
protected static final Logger LOG = LogManager.getLogger(ModelAccess.class);
protected static ModelAccess ourInstance = new DefaultModelAccess();
private final ReentrantReadWriteLockEx myReadWriteLock = new ReentrantReadWriteLockEx();
//ModelAccess is a singleton, so we can omit remove() here though the field is not static
private ThreadLocal<Boolean> myReadEnabledFlag = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
/**
* @deprecated
* @see #getRepositoryStateCache(String)
*/
@Deprecated
protected final ConcurrentHashMap<String, ConcurrentMap<Object, Object>> myRepositoryStateCaches = new ConcurrentHashMap<String, ConcurrentMap<Object, Object>>();
protected ModelAccess() {
}
/**
* It is better to use {@link org.jetbrains.mps.openapi.module.SRepository#getModelAccess()} method to get
* the repository access.
* @deprecated
* @since 3.1
*/
@Deprecated
@ToRemove(version = 3.3)
public static ModelAccess instance() {
return ourInstance;
}
static void setInstance(@NotNull ModelAccess modelAccess) {
ourInstance = modelAccess;
}
protected Lock getReadLock() {
return myReadWriteLock.readLock();
}
protected Lock getWriteLock() {
return myReadWriteLock.writeLock();
}
public boolean hasScheduledWrites() {
return myReadWriteLock.hasScheduledWrites();
}
@Override
public boolean canRead() {
return isReadEnabledFlag() || myReadWriteLock.getReadHoldCount() != 0 || myReadWriteLock.isWriteLockedByCurrentThread();
}
@Override
public boolean canWrite() {
return myReadWriteLock.isWriteLockedByCurrentThread();
}
@Override
public void checkReadAccess() {
if (!canRead()) {
throw new IllegalModelAccessError("You can read model only inside read actions");
}
}
@Override
public void checkWriteAccess() {
if (!canWrite()) {
throw new IllegalModelAccessError("You can write model only inside write actions");
}
}
@Override
public void executeCommand(Runnable r) {
// ExecuteCommandStatement with repo == null generates into executeCommand(Runnable)
executeCommand(r, null);
}
@Override
public void executeCommandInEDT(Runnable r) {
throw new UnsupportedOperationException();
}
@Override
public void executeUndoTransparentCommand(Runnable r) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCommandAction() {
return isInsideCommand();
}
protected void onCommandStarted() {
UnregisteredNodes.instance().enable();
ImmatureReferences.getInstance().enable();
}
protected void onCommandFinished() {
ImmatureReferences.getInstance().disable();
UnregisteredNodes.instance().disable();
}
@Override
@Deprecated
public boolean setReadEnabledFlag(boolean flag) {
Boolean oldValue = myReadEnabledFlag.get();
myReadEnabledFlag.set(flag);
return oldValue;
}
private boolean isReadEnabledFlag() {
return Boolean.TRUE == myReadEnabledFlag.get();
}
/**
* Stores a thread-safe map with user objects.
* @return userObject for a specific key
* @deprecated clients rely on the fact that their cache value needs to be cleared only at the start of write action.
* This is wrong almost always inside the write action.
* Use {@link org.jetbrains.mps.openapi.repository.WriteActionListener} if necessary. Although listening to specific events is still more preferable.
* This mechanism was designed as a hack.
*/
@SuppressWarnings("unchecked")
@Override
@NotNull
@Deprecated
@ToRemove(version = 3.2)
public <K, V> ConcurrentMap<K, V> getRepositoryStateCache(String repositoryKey) {
checkReadAccess();
// NOTE: this change below made the caches invalid within write action
// if (canWrite()) {
// return null;
// }
ConcurrentMap<K, V> cache = (ConcurrentMap<K, V>) myRepositoryStateCaches.get(repositoryKey);
if (cache != null) {
return cache;
}
cache = new ConcurrentHashMap<K, V>();
ConcurrentHashMap<K, V> existingCache = (ConcurrentHashMap<K, V>) myRepositoryStateCaches.putIfAbsent(repositoryKey, (ConcurrentMap<Object, Object>) cache);
return existingCache != null ? existingCache : cache;
}
/**
* called at the start of write action
* @deprecated
* @see #getRepositoryStateCache(String)
*/
@Deprecated
public void clearRepositoryStateCaches() {
LOG.debug("Clearing repository state caches");
myRepositoryStateCaches.clear();
}
/**
* @deprecated use {@link org.jetbrains.mps.openapi.module.ModelAccess#addWriteActionListener}
*/
@Deprecated
public void addWriteActionListener(@NotNull WriteActionListener listener) {
myWriteActionDispatcher.addWriteActionListener(listener);
}
/**
* @deprecated use {@link org.jetbrains.mps.openapi.module.ModelAccess#removeWriteActionListener}
*/
@Deprecated
public void removeWriteActionListener(@NotNull WriteActionListener listener) {
myWriteActionDispatcher.removeWriteActionListener(listener);
}
private static class ReentrantReadWriteLockEx extends ReentrantReadWriteLock {
public ReentrantReadWriteLockEx() {
super(true);
}
public boolean hasScheduledWrites() {
return !this.getQueuedWriterThreads().isEmpty();
}
}
}