/*
* 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.jps.model;
import jetbrains.mps.baseLanguage.search.MPSBaseLanguage;
import jetbrains.mps.classloading.CustomClassLoadingFacet;
import jetbrains.mps.core.platform.Platform;
import jetbrains.mps.core.platform.PlatformFactory;
import jetbrains.mps.core.platform.PlatformOptionsBuilder;
import jetbrains.mps.extapi.module.ModuleFacetBase;
import jetbrains.mps.extapi.module.SRepositoryExt;
import jetbrains.mps.idea.core.make.MPSMakeConstants;
import jetbrains.mps.idea.core.module.CachedModuleData;
import jetbrains.mps.idea.core.module.CachedRepositoryData;
import jetbrains.mps.jps.build.MPSCompilerUtil;
import jetbrains.mps.jps.persistence.CachedDefaultModelRoot;
import jetbrains.mps.jps.persistence.CachedJavaClassStubsModelRoot;
import jetbrains.mps.jps.project.JpsLibSolution;
import jetbrains.mps.jps.project.JpsMPSProject;
import jetbrains.mps.jps.project.JpsSolutionIdea;
import jetbrains.mps.library.ModulesMiner;
import jetbrains.mps.library.ModulesMiner.ModuleHandle;
import jetbrains.mps.persistence.MementoImpl;
import jetbrains.mps.persistence.PersistenceRegistry;
import jetbrains.mps.project.ModuleId;
import jetbrains.mps.project.structure.modules.ModuleFacetDescriptor;
import jetbrains.mps.project.structure.modules.SolutionDescriptor;
import jetbrains.mps.smodel.BaseMPSModuleOwner;
import jetbrains.mps.smodel.MPSModuleOwner;
import jetbrains.mps.smodel.ModuleRepositoryFacade;
import jetbrains.mps.util.SNodeOperations;
import jetbrains.mps.util.io.ModelInputStream;
import jetbrains.mps.vfs.FileRefresh;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.messages.BuildMessage.Kind;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.model.JpsProject;
import org.jetbrains.jps.model.java.JpsJavaSdkType;
import org.jetbrains.jps.model.library.JpsLibrary;
import org.jetbrains.jps.model.module.JpsDependencyElement;
import org.jetbrains.jps.model.module.JpsModule;
import org.jetbrains.jps.model.module.JpsSdkDependency;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.persistence.Memento;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import org.jetbrains.mps.openapi.persistence.ModelRootFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* evgeny, 12/3/12
*/
public class JpsMPSRepositoryFacade implements MPSModuleOwner {
private static final JpsMPSRepositoryFacade INSTANCE = new JpsMPSRepositoryFacade();
public static final UUID JDK_UUID = UUID.fromString("6354ebe7-c22a-4a0f-ac54-50b52ab9b065");
private static final BaseMPSModuleOwner OWNER = new BaseMPSModuleOwner() {};
private Platform myPlatform;
private MPSBaseLanguage myMPSBaseLanguage;
private volatile boolean isInitialized = false;
private CachedRepositoryData myRepo;
private Map<JpsModule, JpsSolutionIdea> myJpsToMpsModules = new HashMap<JpsModule, JpsSolutionIdea>();
private JpsMPSProject myProject;
private SRepository myRepository;
public JpsMPSRepositoryFacade() {
}
public static JpsMPSRepositoryFacade getInstance() {
return INSTANCE;
}
public void init(final CompileContext context) {
if (isInitialized) {
return;
}
initMPS();
myProject = new JpsMPSProject(context.getProjectDescriptor().getProject());
myRepository = myProject.getRepository();
myRepository.getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
long start = System.nanoTime();
initRepository(context,
context.getBuilderParameter(MPSMakeConstants.MPS_LANGUAGES.toString()),
context.getBuilderParameter(MPSMakeConstants.MPS_REPOSITORY.toString()));
initProject(context);
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "MPS loaded in " + (System.nanoTime() - start) / 1000000 + " ms"));
}
isInitialized = true;
}
});
}
public JpsMPSProject getProject() {
return myProject;
}
public JpsSolutionIdea getSolution(JpsModule module) {
if (!isInitialized) throw new IllegalStateException("Not initialized yet");
return myJpsToMpsModules.get(module);
}
private void initRepository(CompileContext context, String languages, String repoFile) {
final ModuleRepositoryFacade repoFacade = new ModuleRepositoryFacade(myRepository);
if (repoFile != null) {
File f = new File(repoFile);
ModelInputStream mos = null;
try {
long start = System.nanoTime();
mos = new ModelInputStream(new FileInputStream(f));
myRepo = CachedRepositoryData.load(mos);
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "loaded " + myRepo.getModules().size() + " modules in " + (System.nanoTime() - start) / 1000000 + " ms"));
}
// use optimized implementation of default model root
PersistenceRegistry.getInstance().setModelRootFactory(PersistenceRegistry.DEFAULT_MODEL_ROOT, new ModelRootFactory() {
@NotNull
@Override
public ModelRoot create() {
return new CachedDefaultModelRoot(myRepo);
}
});
PersistenceRegistry.getInstance().setModelRootFactory(PersistenceRegistry.JAVA_CLASSES_ROOT, new ModelRootFactory() {
@NotNull
@Override
public ModelRoot create() {
return new CachedJavaClassStubsModelRoot(myRepo);
}
});
start = System.nanoTime();
for (CachedModuleData data : myRepo.getModules()) {
repoFacade.instantiateModule(data.getHandle(), this);
}
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "instantiated " + myRepo.getModules().size() + " modules in " + (System.nanoTime() - start) / 1000000 + " ms"));
}
return;
} catch (IOException e) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, e));
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.WARNING, "cannot load cache, generation may be slow"));
} finally {
jetbrains.mps.util.FileUtil.closeFileSafe(mos);
}
} else if (languages != null) {
// TODO split by semicolon, etc.
long start = System.nanoTime();
List<ModuleHandle> loadedModules = new ArrayList<ModuleHandle>();
List<IFile> filesToLoad = new ArrayList<>();
for (String path: languages.split(";")) {
IFile ipath = FileSystem.getInstance().getFileByPath(path);
filesToLoad.add(ipath);
}
new FileRefresh(filesToLoad).run();
ModulesMiner modulesMiner = new ModulesMiner();
for (IFile ipath : filesToLoad) {
modulesMiner.collectModules(ipath);
}
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "loaded " + modulesMiner.getCollectedModules().size() + " modules in " + (System.nanoTime() - start) / 1000000 + " ms"));
}
start = System.nanoTime();
for (ModuleHandle moduleHandle : modulesMiner.getCollectedModules()) {
repoFacade.instantiateModule(moduleHandle, OWNER);
}
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "instantiated " + modulesMiner.getCollectedModules().size() + " modules in " + (System.nanoTime() - start) / 1000000 + " ms"));
}
return;
}
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.WARNING, "cannot start MPS, no repository provided"));
}
private void initProject(CompileContext context) {
long start = System.nanoTime();
JpsProject jpsProject = context.getProjectDescriptor().getProject();
Set<JpsLibrary> processedSdks = new HashSet<JpsLibrary>();
JpsLibrary jdk = null;
for (JpsModule mod : jpsProject.getModules()) {
JpsMPSModuleExtension extension = JpsMPSExtensionService.getInstance().getExtension(mod);
if (extension == null) {
continue;
}
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "Creating solution for " + mod.getName()));
}
SolutionDescriptor descriptor = extension.getConfiguration().getSolutionDescriptor();
descriptor.setNamespace(mod.getName());
MPSCompilerUtil.debug(context, "UUID " + descriptor.getId());
// Commeted out. See SolutionIdea: solutions don't have foreign ids, rather regular
// descriptor.setId(ModuleId.foreign(mod.getName()));
JpsSolutionIdea module = new JpsSolutionIdea(mod, descriptor, context);
JpsSolutionIdea solutionIdea = ((SRepositoryExt) myRepository).registerModule(module, myProject);
if (module == solutionIdea) {
solutionIdea.updateModelsSet();
}
myProject.addModule(solutionIdea);
myJpsToMpsModules.put(mod, solutionIdea);
// let's handle module sdkLib
for (JpsLibrary sdk: getModuleSdks(mod, context)) {
MPSCompilerUtil.debug(context, "SDK name" + sdk.getName() + " type: " + sdk.getType());
JpsLibSolution sdkSolution = createLibSolution(sdk, jdk, context);
JpsLibSolution regSolution = ((SRepositoryExt) myRepository).registerModule(sdkSolution, myProject);
MPSCompilerUtil.debug(context, "SDK " + regSolution.getModuleReference().toString());
if (sdkSolution == regSolution) {
MPSCompilerUtil.debug(context, "SDK updating model set for " + sdk.getName());
sdkSolution.updateModelsSet();
}
if (JpsJavaSdkType.INSTANCE.equals(sdk.getType()) && !processedSdks.contains(sdk)) {
jdk = jdk != null ? jdk : sdk;
processedSdks.add(sdk);
}
}
}
if (processedSdks.size() > 1) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.ERROR, "Different SDKs in modules with MPS facets are not supported"));
}
// maybe libraries should be put into repository before modules, so that SolutionIdea already has its dependencies at hand
for (JpsLibrary jpsLib : jpsProject.getLibraryCollection().getLibraries()) {
JpsLibSolution libSolution = createLibSolution(jpsLib, jdk, context);
JpsLibSolution regSolution = ((SRepositoryExt) myRepository).registerModule(libSolution, myProject);
MPSCompilerUtil.debug(context, "LIB " + regSolution.getModuleReference().toString());
if (libSolution == regSolution) {
MPSCompilerUtil.debug(context, "LIB updating model set for " + jpsLib.getName());
libSolution.updateModelsSet();
}
if (MPSCompilerUtil.isExtraTracingMode()) {
for (SModel desc : regSolution.getModels()) {
MPSCompilerUtil.debug(context, "LIB model " + desc.getModelName());
}
}
}
if (MPSCompilerUtil.isTracingMode()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "Project modules loaded in " + (System.nanoTime() - start) / 1000000 + " ms"));
if (MPSCompilerUtil.isExtraTracingMode()) {
for (SModule m : new ModuleRepositoryFacade(myRepository).getModules(myProject, null)) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "Debug output: module " + m.getModuleReference().toString()));
for (SModel d : m.getModels()) {
context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "Debug output: model " + SNodeOperations.getModelLongName(d) + " / " + d.getReference().toString()));
// It makes model loading non-lazy and kills the whole thing if stubs are built for everything (like SDK, libs, etc)
// for (SNode n : d.getRootNodes()) {
// context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "node: " + n.getName() + " id: " + n.getSNodeId().toString()));
// if (n.getName().equals("PsiListener") || n.getName().equals("PsiChangesWatcher")) {
// for (SNode n2 : n.getChildren()) {
// context.processMessage(new CompilerMessage(MPSMakeConstants.BUILDER_ID, Kind.INFO, "child: " + n2.getName() + " id: " + n2.getSNodeId().toString()));
// }
// }
// }
}
}
}
}
}
private JpsLibSolution createLibSolution(JpsLibrary lib, JpsLibrary jdk, CompileContext ctx) {
String name = lib.getName();
SolutionDescriptor desc = new SolutionDescriptor();
desc.setNamespace(name);
if (JpsJavaSdkType.INSTANCE.equals(lib.getType()) && jdk == null) {
ModuleId jdkId = ModuleId.regular(JDK_UUID);
SModule existingModule = myRepository.getModule(jdkId);
if (existingModule != null) {
desc.setNamespace(existingModule.getModuleName());
CustomClassLoadingFacet facet = existingModule.getFacet(CustomClassLoadingFacet.class);
assert facet != null;
Memento memento = new MementoImpl();
facet.save(memento);
desc.getModuleFacetDescriptors().add(new ModuleFacetDescriptor(((ModuleFacetBase) facet).getFacetType(), memento));
Set<MPSModuleOwner> owners = new HashSet<MPSModuleOwner>(new ModuleRepositoryFacade(myRepository).getModuleOwners(existingModule));
for (MPSModuleOwner owner : owners) {
// if (owner == this) continue;
// fixme wanted to used ModuleRepositoryFacade but it doesn't have exactly this method
((SRepositoryExt) myRepository).unregisterModule(existingModule, owner);
}
}
desc.setId(jdkId);
} else {
desc.setId(ModuleId.foreign(name));
}
return new JpsLibSolution(desc, lib, jdk, ctx);
}
private List<JpsLibrary> getModuleSdks(JpsModule module, CompileContext ctx) {
List<JpsLibrary> sdks = new ArrayList<JpsLibrary>();
for (JpsDependencyElement dep : module.getDependenciesList().getDependencies()) {
if (!(dep instanceof JpsSdkDependency)) continue;
JpsLibrary lib = ((JpsSdkDependency) dep).resolveSdk();
if (lib != null) {
sdks.add(lib);
}
}
return sdks;
}
public void dispose() {
if (!isInitialized) {
return;
}
myRepository.getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
new ModuleRepositoryFacade(myRepository).unregisterModules(myProject);
myProject.dispose();
myJpsToMpsModules.clear();
myRepo = null;
}
});
disposeMPS();
isInitialized = false;
}
private void initMPS() {
myPlatform = PlatformFactory.initPlatform(PlatformOptionsBuilder.ALL);
myMPSBaseLanguage = new MPSBaseLanguage();
myMPSBaseLanguage.init();
}
private void disposeMPS() {
myMPSBaseLanguage.dispose();
myPlatform.dispose();
}
@Override
public boolean isHidden() {
return true;
}
}