/*
* 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.library;
import jetbrains.mps.components.CoreComponent;
import jetbrains.mps.library.contributor.LibDescriptor;
import jetbrains.mps.library.contributor.LibraryContributor;
import jetbrains.mps.library.contributor.RepositoryContributor;
import jetbrains.mps.util.annotation.ToRemove;
import jetbrains.mps.vfs.FileRefresh;
import jetbrains.mps.vfs.IFile;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.module.ModelAccess;
import org.jetbrains.mps.openapi.module.SRepository;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
* An implementation of RepositoryReader, which is lazy (in a way that it tries not load the same module twice)
* At the same time it creates SLibrary for each path {@link RepositoryContributor#getPaths()} returns.
* FIXME need to separate up these two.
*/
public final class LibraryInitializer implements CoreComponent, RepositoryReader<LibraryContributor> {
private static final Logger LOG = LogManager.getLogger(LibraryInitializer.class);
// fixme get rid of
private static LibraryInitializer INSTANCE;
// fixme get rid of
public static LibraryInitializer getInstance() {
return INSTANCE;
}
private final SRepository myRepository;
private final ModelAccess myModelAccess;
private final List<LibraryContributor> myContributors = new CopyOnWriteArrayList<LibraryContributor>();
private final Set<SLibrary> myLibraries = new LinkedHashSet<SLibrary>();
@Override
public void init() {
if (INSTANCE != null) {
throw new IllegalStateException("double initialization");
}
INSTANCE = this;
}
@Override
public void dispose() {
// we are going to remove modules from the repository, need exclusive access
myModelAccess.runWriteAction(() -> {
for (SLibrary lib : myLibraries) {
lib.dispose();
}
myLibraries.clear();
myContributors.clear();
});
INSTANCE = null;
}
public LibraryInitializer(@NotNull SRepository repository) {
myRepository = repository;
myModelAccess = repository.getModelAccess();
}
/**
* EDT is required
* @deprecated use {@link #load(List)} instead
*/
@Override
@ToRemove(version = 2017.3)
@Deprecated
public void loadRefreshed(List<LibraryContributor> contributors) {
for (LibraryContributor contributor : contributors) {
addContributor(contributor);
}
update(true);
}
@Override
public void load(List<LibraryContributor> contributors) {
for (LibraryContributor contributor : contributors) {
addContributor(contributor);
}
update(false);
}
@Override
public void unload(List<LibraryContributor> contributors) {
for (LibraryContributor contributor : contributors) {
removeContributor(contributor);
}
update(false);
}
/**
* @deprecated please use one-step loading methods: {@link #loadRefreshed} or {@link #load}
*/
@Deprecated
public void update() {
update(false);
}
/**
* not intended to be called explicitly anymore.
* @see #update()
* @param refreshFiles if true, then the caller needs to handle EDT lock, because deeper the synchronous recursive file system refreshLibRoots would be called.
* FIXME need to get rid of that synchronous refreshLibRoots
*
*/
@Deprecated
public void update(final boolean refreshFiles) {
myModelAccess.runWriteAction(new Runnable() {
@Override
public void run() {
final Set<SLibrary> currentLibs = new HashSet<SLibrary>();
List<LibraryContributor> contributors = myContributors;
for (LibraryContributor contributor : contributors) {
boolean hidden = contributor.hiddenLanguages();
for (LibDescriptor pathDescriptor : contributor.getPaths()) {
SLibrary lib = new SLibrary(myRepository, pathDescriptor, hidden);
currentLibs.add(lib);
}
}
final Delta<SLibrary> libraryDelta = Delta.construct(myLibraries, currentLibs);
if (libraryDelta.isEmpty()) return;
updateState(refreshFiles, libraryDelta);
libraryDelta.apply(myLibraries);
}
});
}
// performed in write action
// actual reading from disk happens here
private void updateState(final boolean refreshFiles, Delta<SLibrary> libraryDelta) {
myModelAccess.checkWriteAccess();
final List<SLibrary> toUnload = libraryDelta.getRemoved();
final List<SLibrary> toLoad = libraryDelta.getAdded();
printStatus(toUnload, toLoad);
for (SLibrary unloadLib : toUnload) {
unloadLib.dispose();
}
if (refreshFiles) {
refreshLibRoots(toLoad);
}
for (SLibrary loadLib : toLoad) {
loadLib.attach();
}
LOG.info("Library update is finished");
}
private void printStatus(List<SLibrary> toUnload, List<SLibrary> toLoad) {
String message = "";
if (!toLoad.isEmpty()) {
message = String.format("Loading %d libraries", toLoad.size());
message += toUnload.isEmpty() ? "" : "; ";
}
if (!toUnload.isEmpty()) {
message += String.format("Unloading %d libraries", toUnload.size());
}
LOG.info(message);
}
private void refreshLibRoots(List<SLibrary> toLoad) {
List<IFile> collect = toLoad.stream().map(SLibrary::getFile).collect(Collectors.toList());
new FileRefresh(collect).run();
}
//----------bootstrap modules
// used in plugin; TODO remove
@Deprecated
public List<ModulesMiner.ModuleHandle> getModuleHandles() {
myModelAccess.checkReadAccess();
List<ModulesMiner.ModuleHandle> result = new ArrayList<ModulesMiner.ModuleHandle>();
for (SLibrary lib : myLibraries) {
result.addAll(lib.getHandles());
}
return result;
}
/**
* Please use one-step version to load modules from disk to MPS {@link #load(List)} or {@link #loadRefreshed(List)}
*/
@Deprecated
private void addContributor(@NotNull LibraryContributor c) {
LOG.info("Adding libraries from " + c.getClass().getSimpleName());
myContributors.add(c);
}
/**
* Please use one-step version to unload modules from MPS {@link #unload(List)}
*/
@Deprecated
private void removeContributor(@NotNull LibraryContributor c) {
LOG.info("Removing libraries from " + c.getClass().getSimpleName());
myContributors.remove(c);
}
private static class Delta<T extends Comparable<T>> {
private final Set<T> myAdded;
private final Set<T> myRemoved;
public static <T extends Comparable<T>> Delta<T> construct(Collection<T> initial, Collection<T> updated) {
Set<T> added = subtractSets(updated, initial);
Set<T> removed = subtractSets(initial, updated);
return new Delta<T>(added, removed);
}
private static <T> Set<T> subtractSets(Collection<T> s1, Collection<T> s2) {
Set<T> set1 = new HashSet<T>(s1);
set1.removeAll(s2);
return set1;
}
private Delta(Collection<T> added, Collection<T> removed) {
myAdded = new HashSet<T>(added);
myRemoved = new HashSet<T>(removed);
}
public List<T> getAdded() {
return createSortedList(myAdded);
}
public List<T> getRemoved() {
return createSortedList(myRemoved);
}
private static <T extends Comparable<T>> List<T> createSortedList(Set<T> added) {
List<T> list = new ArrayList<T>(added);
Collections.sort(list);
return list;
}
public boolean isEmpty() {
return myAdded.isEmpty() && myRemoved.isEmpty();
}
public void apply(Collection<T> toChange) {
toChange.removeAll(myRemoved);
toChange.addAll(myAdded);
}
}
}