/*
* 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.library.ModulesMiner.ModuleHandle;
import jetbrains.mps.library.contributor.LibDescriptor;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.smodel.Generator;
import jetbrains.mps.smodel.Language;
import jetbrains.mps.smodel.MPSModuleOwner;
import jetbrains.mps.smodel.ModuleRepositoryFacade;
import jetbrains.mps.util.EqualUtil;
import jetbrains.mps.vfs.FileListener;
import jetbrains.mps.vfs.FileSystemEvent;
import jetbrains.mps.vfs.IFile;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* SLibrary tracks a path {@link #myFile} with modules inside.
* It listens to file system events and reloads modules from disk if necessary.
* It is actually the layer between SRepository and SModule in the repository hierarchy (as well as the Project).
* [The repository consists from library modules, project modules and several special modules (there are few of them)]
*
* evgeny, 11/3/12
*/
public class SLibrary implements FileListener, MPSModuleOwner, Comparable<SLibrary> {
private static final Logger LOG = Logger.getLogger(SLibrary.class);
@NotNull private final IFile myFile;
private final SRepository myRepository;
private final ClassLoader myPluginClassLoader;
private final boolean myHidden;
private final AtomicReference<List<ModuleHandle>> myHandles = new AtomicReference<>();
private ModuleFileTracker myFileTracker;
public SLibrary(@NotNull SRepository repoToUpdate, LibDescriptor pathDescriptor, boolean hidden) {
myRepository = repoToUpdate;
myPluginClassLoader = pathDescriptor.getPluginClassLoader();
myFile = pathDescriptor.getPath();
myHidden = hidden;
// SLibrary listens to file changes as it needs to react to create events anyway. ModuleFileTracker is a storage + change/delete handler.
myFileTracker = new ModuleFileTracker(false);
}
@NotNull
public IFile getFile() {
return myFile;
}
/**
* @return a classloader which will be the parent for all ModuleClassLoaders created for the modules in this SLibrary
*/
@Nullable
public ClassLoader getPluginClassLoader() {
return myPluginClassLoader;
}
// TODO transfer these methods up to the {@link RepositoryReader}
List<ModuleHandle> getHandles() {
List<ModuleHandle> moduleHandles = myHandles.get();
if (moduleHandles == null) return Collections.emptyList();
return moduleHandles;
}
void attach() {
LOG.debug("Attaching " + this);
// without this the performance drops because of the heavy idea local filesystem listening mechanism
myFile.addListener(this);
collectAndRegisterModules();
}
void dispose() {
LOG.debug("Disposing " + this);
ModuleRepositoryFacade.getInstance().unregisterModules(this);
myFile.removeListener(this);
}
@Override
public void update(ProgressMonitor monitor, @NotNull FileSystemEvent event) {
// FIXME update() comes with global model write lock. This code might have better idea about what to lock
// (although write per jar file is not the best alternative. SLibrary not always a directory, it's a single jar e.g. in Ant::generate).
myFileTracker.update(monitor, event);
// XXX Note, removed modules do not update myHandles (as it used to be with AbstractModule listening to changes). Perhaps, shall clean
// respective ModuleHandles here, as well.
for (IFile f : event.getCreated()) {
if (ModulesMiner.isSourceModuleFile(f)) {
collectAndRegisterModules();
return;
}
}
}
private void collectAndRegisterModules() {
final ModulesMiner modulesMiner = new ModulesMiner().collectModules(myFile);
List<ModuleHandle> moduleHandles = new ArrayList<>(modulesMiner.getCollectedModules());
ModuleRepositoryFacade mrf = new ModuleRepositoryFacade(myRepository);
myHandles.set(moduleHandles);
List<SModule> loaded = new ArrayList<>();
for (ModuleHandle moduleHandle : moduleHandles) {
SModule module = mrf.instantiateModule(moduleHandle, this);
loaded.add(module);
myFileTracker.track(moduleHandle.getFile(), module);
}
for (SModule module : loaded) {
// FIXME if there are generators among loaded, Generator.onModuleLoad() is invoked twice.
((AbstractModule) module).onModuleLoad();
if (module instanceof Language) {
((Language) module).getGenerators().forEach(Generator::onModuleLoad);
}
}
}
@Override
public boolean isHidden() {
return myHidden;
}
@Override
public String toString() {
return "SLibrary [path " + myFile + "; plugin " + myPluginClassLoader + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SLibrary library = (SLibrary) o;
if (myHidden != library.myHidden) return false;
return EqualUtil.equals(myPluginClassLoader, library.myPluginClassLoader) && myFile.equals(library.myFile);
}
@Override
public int hashCode() {
int result = myFile.hashCode();
result = 31 * result + (myPluginClassLoader != null ? myPluginClassLoader.hashCode() : 0);
result = 31 * result + (myHidden ? 1 : 0);
return result;
}
@Override
public int compareTo(@NotNull SLibrary another) {
String n1 = getFile().getName();
String n2 = another.getFile().getName();
// when comparing generator module, ensure it goes after any other non-generator module, to ensure languages come first.
// At the moment, ModuleRepositoryFacade is not capable to register generator for a language not yet known to the repo.
// Also see ModulesMiner.getCollectedModules()
if (n1.endsWith("-generator.jar") ^ n2.endsWith("-generator.jar")) {
return n1.endsWith("-generator.jar") ? 1 : -1;
}
return n1.compareTo(n2);
}
}