/*
* Copyright 2003-2016 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.nodefs;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SRepository;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* Manages {@linkplain com.intellij.openapi.vfs.VirtualFile virtual files} for nodes of given repository
*
* @author Artem Tikhomirov
* @since 3.4
*/
final class RepositoryVirtualFiles {
private final NodeVirtualFileSystem myFileSystem;
private final SRepository myRepository;
private Map<SNodeReference, MPSNodeVirtualFile> myVirtualFiles = new ConcurrentHashMap<SNodeReference, MPSNodeVirtualFile>();
private Map<SModelReference, MPSModelVirtualFile> myModelVirtualFiles = new ConcurrentHashMap<SModelReference, MPSModelVirtualFile>();
private final NiceReferenceSerializer myPathFacility;
/*package*/ RepositoryVirtualFiles(@NotNull NodeVirtualFileSystem mpsFileSystem, @NotNull SRepository repository) {
myFileSystem = mpsFileSystem;
myRepository = repository;
myPathFacility = new NiceReferenceSerializer(repository);
}
/**
* make this virtual file provider known in the {@linkplain NodeVirtualFileSystem file system}
*/
public void register() {
myFileSystem.register(this);
}
/**
* Tell {@linkplain NodeVirtualFileSystem node file system} not to use files of this repository
*/
public void unregister() {
myFileSystem.unregister(this);
}
public void clear() {
myVirtualFiles.clear();
myModelVirtualFiles.clear();
}
@NotNull
/*package*/ SRepository getRepository() {
return myRepository;
}
@NotNull
/*package*/ NodeVirtualFileSystem getFileSystem() {
return myFileSystem;
}
/*package*/ NiceReferenceSerializer getPathFacility() {
return myPathFacility;
}
public MPSNodeVirtualFile getFileFor(@NotNull final SNodeReference nodePointer) {
if (hasVirtualFileFor(nodePointer)) {
return getVirtualFile(nodePointer);
}
MPSNodeVirtualFile vf = new MPSNodeVirtualFile(nodePointer, this);
myVirtualFiles.put(nodePointer, vf);
return vf;
}
public MPSModelVirtualFile getFileFor(@NotNull final SModelReference modelReference) {
if (myModelVirtualFiles.containsKey(modelReference)) {
return myModelVirtualFiles.get(modelReference);
}
final MPSModelVirtualFile vf = new MPSModelVirtualFile(modelReference, this);
myModelVirtualFiles.put(modelReference, vf);
return vf;
}
/*package*/ boolean hasVirtualFileFor(SNodeReference nodePointer) {
return myVirtualFiles.containsKey(nodePointer);
}
/**
* @return existing VF, if any.
*/
@Nullable
/*package*/ MPSNodeVirtualFile getVirtualFile(SNodeReference nodeRef) {
return myVirtualFiles.get(nodeRef);
}
// XXX likely, RVF shall be responsible to collect deleted/renamed files, rather than give access to known vf
/*package*/ Collection<MPSNodeVirtualFile> getKnownVirtualFilesIn(SModelReference modelRef) {
ArrayList<MPSNodeVirtualFile> rv = new ArrayList<MPSNodeVirtualFile>();
for (MPSNodeVirtualFile vf : myVirtualFiles.values()) {
if (modelRef.equals(vf.getSNodePointer().getModelReference())) {
rv.add(vf);
}
}
return rv;
}
/*package*/ void forgetVirtualFile(SNodeReference nodeRef) {
myVirtualFiles.remove(nodeRef);
}
@Nullable
/*package*/ VirtualFile findFileByPath(final @NotNull String path) {
try {
if (path.startsWith(MPSNodeVirtualFile.NODE_PREFIX)) {
SNode node = getPathFacility().deserializeNode(path.substring(MPSNodeVirtualFile.NODE_PREFIX.length()));
if (node == null) {
return null;
}
return getFileFor(node.getReference());
} else if (path.startsWith(MPSModelVirtualFile.MODEL_PREFIX)) {
SModel model = getPathFacility().deserializeModel(path.substring(MPSModelVirtualFile.MODEL_PREFIX.length()));
if (model == null) {
return null;
}
return getFileFor(model.getReference());
}
} catch (IllegalArgumentException e) {
// ignore, parse model ref exception
}
return null;
}
/*
* There are 3 valid states:
* (null, false) initial and the moment notifier is processed, so any requestors would receive new instance
* (notifier, false) notifier is registered but not yet scheduled
* (notifier, true) notifier was scheduled, but not yet processed.
*/
private final AtomicMarkableReference<Runnable> myNotifier = new AtomicMarkableReference<>(null, false);
/*package*/ <T extends Runnable> T getNotifier(T notifier) {
assert notifier != null;
if (myNotifier.compareAndSet(null, notifier, false, false)) {
return notifier;
} else {
@SuppressWarnings("unchecked")
T existing = (T) myNotifier.getReference();
// generally, existing shall not be null, just in case getNotifier is invoked in parallel with
// model write right after myNotifier has been cleared. Later, else in the scheduleNotifier would yield another write for notifier.
return existing == null ? notifier : existing;
}
}
/*package*/ <T extends Runnable> void scheduleNotifier(T notifier) {
assert notifier != null;
if (myNotifier.compareAndSet(notifier, notifier, false, true)) {
myRepository.getModelAccess().runWriteInEDT(() -> {
if (myNotifier.compareAndSet(notifier, null, true, false)) {
notifier.run();
}
});
} else if (notifier != myNotifier.getReference()) {
// fallback, just in case there's untracked notifier, run it anyway not to loose any notification
myRepository.getModelAccess().runReadInEDT(notifier);
}
}
}