/*
* 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.idea.core.project;
import com.intellij.ProjectTopics;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.facet.FacetManagerAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.JdkOrderEntry;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.RootProvider;
import com.intellij.openapi.roots.RootProvider.RootSetChangedListener;
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable.Listener;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.CommonProcessors.FindProcessor;
import com.intellij.util.Processor;
import com.intellij.util.messages.MessageBusConnection;
import jetbrains.mps.extapi.module.ModuleFacetBase;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.facet.MPSFacet;
import jetbrains.mps.idea.core.facet.MPSFacetType;
import jetbrains.mps.idea.core.library.ModuleLibrariesUtil;
import jetbrains.mps.idea.core.library.ModuleLibraryType;
import jetbrains.mps.idea.core.project.module.ModuleMPSSupport;
import jetbrains.mps.idea.core.project.stubs.DifferentSdkException;
import jetbrains.mps.idea.core.project.stubs.JdkStubSolutionManager;
import jetbrains.mps.idea.core.project.stubs.MultipleSdkProblemNotifier;
import jetbrains.mps.idea.core.psi.impl.PsiModelReloadListener;
import jetbrains.mps.module.SDependencyImpl;
import jetbrains.mps.project.ModuleId;
import jetbrains.mps.project.Solution;
import jetbrains.mps.project.facets.JavaModuleFacet;
import jetbrains.mps.project.facets.JavaModuleFacetImpl;
import jetbrains.mps.project.structure.modules.Dependency;
import jetbrains.mps.project.structure.modules.ModuleDescriptor;
import jetbrains.mps.project.structure.modules.SolutionDescriptor;
import jetbrains.mps.smodel.SModelAdapter;
import jetbrains.mps.smodel.SModelInternal;
import jetbrains.mps.smodel.event.SModelLanguageEvent;
import jetbrains.mps.smodel.event.SModelListener;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.module.ModelAccess;
import org.jetbrains.mps.openapi.module.SDependency;
import org.jetbrains.mps.openapi.module.SDependencyScope;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleListener;
import org.jetbrains.mps.openapi.module.SModuleListenerBase;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.persistence.Memento;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class SolutionIdea extends Solution {
@NotNull
private final Module myModule;
private ModelAccess myModelAccess;
private List<SDependency> myDependencies;
private Set<ModelRoot> myContributedModelRoots;
private MessageBusConnection myConnection;
private final SolutionIdea.MyRootSetChangedListener myRootSetListener = new MyRootSetChangedListener();
private final SolutionIdea.MyLibrariesListener myLibrariesListener = new MyLibrariesListener();
public SolutionIdea(@NotNull Module module, SolutionDescriptor descriptor) {
super(descriptor, null);
myModule = module;
myModelAccess = ProjectHelper.getModelAccess(myModule.getProject());
assert myModelAccess != null;
// TODO: simply set solution descriptor local variable?
setModuleDescriptor(descriptor);
myConnection = myModule.getProject().getMessageBus().connect();
myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener());
myConnection.subscribe(FacetManager.FACETS_TOPIC, new MyFacetManagerAdapter());
final ProjectLibraryTable projectLibraryTable = (ProjectLibraryTable) ProjectLibraryTable.getInstance(myModule.getProject());
myModelAccess.runReadAction(new Runnable() {
@Override
public void run() {
for (final Library library : projectLibraryTable.getLibraries()) {
if (ModuleLibraryType.isModuleLibrary(library)) {
library.getRootProvider().addRootSetChangedListener(myRootSetListener);
}
}
for (SModel model : getModels()) {
((SModelInternal) model).addModelListener(MODEL_RUNTIME_IMPORTER);
}
}
});
projectLibraryTable.addListener(myLibrariesListener);
addModuleListener(myModule.getProject().getComponent(PsiModelReloadListener.class));
addModuleListener(MODULE_RUNTIME_IMPORTER);
}
@Override
protected void doSetModuleDescriptor(ModuleDescriptor moduleDescriptor) {
assert moduleDescriptor instanceof SolutionDescriptor;
SolutionDescriptor newDescriptor = (SolutionDescriptor) moduleDescriptor;
myDependencies = null;
myContributedModelRoots = null;
newDescriptor.setNamespace(myModule.getName());
// addLibs(newDescriptor);
super.doSetModuleDescriptor(newDescriptor);
try {
ApplicationManager.getApplication().getComponent(JdkStubSolutionManager.class).claimSdk(myModule);
} catch (final DifferentSdkException e) {
myModule.getProject().getComponent(MultipleSdkProblemNotifier.class).reportSdkProblem(myModule, e);
}
}
@Override
public void dispose() {
final ProjectLibraryTable projectLibraryTable = (ProjectLibraryTable) ProjectLibraryTable.getInstance(myModule.getProject());
myModelAccess.runReadAction(new Runnable() {
@Override
public void run() {
for (final Library library : projectLibraryTable.getLibraries()) {
if (ModuleLibraryType.isModuleLibrary(library)) {
library.getRootProvider().removeRootSetChangedListener(myRootSetListener);
}
}
}
});
projectLibraryTable.removeListener(myLibrariesListener);
ApplicationManager.getApplication().getComponent(JdkStubSolutionManager.class).releaseSdk(myModule);
super.dispose();
myConnection.disconnect();
}
@Override
protected SolutionDescriptor loadDescriptor() {
return getModuleDescriptor();
}
@Override
protected Iterable<ModelRoot> loadRoots() {
if (myContributedModelRoots == null) {
myContributedModelRoots = new HashSet<ModelRoot>();
for (ModelRootContributorEP e : ModelRootContributorEP.EP_NAME.getExtensions()) {
for (ModelRoot root : e.getModelRootContribitor().getModelRoots(myModule)) {
myContributedModelRoots.add(root);
}
}
}
List<ModelRoot> sum = new ArrayList<ModelRoot>();
for (ModelRoot mr : super.loadRoots()) {
sum.add(mr);
}
sum.addAll(myContributedModelRoots);
return sum;
}
@Override
public Iterable<SDependency> getDeclaredDependencies() {
if (myDependencies == null) {
myDependencies = new ArrayList<SDependency>();
ArrayList<Module> usedModules = new ArrayList<Module>(Arrays.asList(ModuleRootManager.getInstance(myModule).getDependencies()));
for (Module usedModule : usedModules) {
MPSFacet usedModuleMPSFacet = FacetManager.getInstance(usedModule).getFacetByType(MPSFacetType.ID);
if (usedModuleMPSFacet != null && usedModuleMPSFacet.wasInitialized()) {
myDependencies.add(new SDependencyImpl(usedModuleMPSFacet.getSolution(), SDependencyScope.DEFAULT, false));
}
}
addUsedSdk(myDependencies);
addUsedLibraries(myDependencies);
// adding JDK module to a set of dependencies
// why, oh, why are we doing it?
// FIXME, PLEASE!
// Solution jdkSolution = StubSolutionIdea.getJdkSolution();
// if (jdkSolution != null) {
// myDependencies.add(new Dependency(jdkSolution.getModuleReference(), false));
// }
}
return myDependencies;
}
private void addUsedSdk(final List<SDependency> dependencies) {
Solution sdkSolution = ApplicationManager.getApplication().getComponent(JdkStubSolutionManager.class).getModuleSdkSolution(myModule);
if (sdkSolution != null) {
dependencies.add(new SDependencyImpl(sdkSolution, SDependencyScope.DEFAULT, false));
}
}
private void addUsedLibraries(final List<SDependency> dependencies) {
dependencies.addAll(calculateLibraryDependencies(ModuleRootManager.getInstance(myModule).orderEntries(), true));
}
public List<SDependency> calculateLibraryDependencies(OrderEnumerator orderEnumerator, final boolean includeStubs) {
final Map<SModule, Boolean> modules = new HashMap<SModule, Boolean>();
orderEnumerator.forEach(new Processor<OrderEntry>() {
public boolean process(OrderEntry oe) {
if (!(oe instanceof LibraryOrderEntry)) {
return true;
}
LibraryOrderEntry loe = (LibraryOrderEntry) oe;
Library library = loe.getLibrary();
if (loe.isModuleLevel() || library == null) {
return true;
}
if (ModuleLibraryType.isModuleLibrary(library)) {
Set<SModuleReference> moduleReferences = ModuleLibrariesUtil.getModules(ProjectHelper.getProjectRepository(myModule.getProject()), library);
for (SModuleReference moduleReference : moduleReferences) {
SModule m = moduleReference.resolve(getRepository());
if (m == null) {
continue;
}
if (modules.containsKey(moduleReference)) {
if (loe.isExported()) {
modules.put(m, true);
}
} else {
modules.put(m, loe.isExported());
}
}
} else if (includeStubs) {
// try to find stub solution
SModule s = getRepository().getModule(ModuleId.foreign(library.getName()));
if (s != null) {
modules.put(s, loe.isExported());
}
}
return true;
}
});
List<SDependency> result = new ArrayList<SDependency>();
for (Entry<SModule, Boolean> entry : modules.entrySet()) {
result.add(new SDependencyImpl(entry.getKey(), SDependencyScope.DEFAULT, entry.getValue()));
}
return result;
}
@Override
public Dependency addDependency(@NotNull SModuleReference moduleRef, boolean reexport) {
// we do not add a dependency into solution, we add dependency to idea module instead
ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(myModule).getModifiableModel();
SModule sModule = moduleRef.resolve(getRepository());
if (sModule instanceof SolutionIdea) {
// we add dependency between idea modules
Module otherIdeaModule = ((SolutionIdea) sModule).getIdeaModule();
modifiableModel.addModuleOrderEntry(otherIdeaModule);
} else {
ModuleRuntimeLibrariesImporter.importForUsedModules(myModule, Collections.singleton(moduleRef), modifiableModel);
}
modifiableModel.commit();
return null;
}
@Override
protected void dependenciesChanged() {
super.dependenciesChanged();
myDependencies = null;
}
@Override
public boolean isPackaged() {
return false;
}
@Override
public void save() {
// TODO: implement saving functionality here.
// should this methods really do something?
// super.save(); //To change body of overridden methods use File | Settings | File Templates.
}
@Override
public IFile getDescriptorFile() {
return getFileSystem().getFile(myModule.getModuleFilePath());
}
private void handleFacetChanged(Facet facet) {
if (skipFacetNotification(facet)) {
return;
}
myModelAccess.runWriteInEDT(new Runnable() {
@Override
public void run() {
setModuleDescriptor(getModuleDescriptor());
}
});
}
private boolean skipFacetNotification(Facet facet) {
if (!myModule.getProject().equals(facet.getModule().getProject())) {
return true;
}
Module[] dependencies = ModuleRootManager.getInstance(myModule).getDependencies();
Module facetModule = facet.getModule();
for (Module dependency : dependencies) {
if (dependency.equals(facetModule)) {
return false;
}
}
return true;
}
@Override
protected ModuleFacetBase setupFacet(ModuleFacetBase facet, Memento memento) {
if (facet instanceof JavaModuleFacet) {
facet = new JavaModuleFacetImpl() {
@Override
public IFile getClassesGen() {
IFile descriptorFile = getDescriptorFile();
if (descriptorFile != null && descriptorFile.isReadOnly()) {
return super.getClassesGen();
}
CompilerModuleExtension compilerModuleExtension = ModuleRootManager.getInstance(myModule).getModuleExtension(CompilerModuleExtension.class);
VirtualFile compilerOutputPath = compilerModuleExtension.getCompilerOutputPath();
if (compilerOutputPath == null) {
return null;
}
return FileSystem.getInstance().getFileByPath(compilerOutputPath.getPath());
}
};
}
return super.setupFacet(facet, memento);
}
private void addLibs(SolutionDescriptor solutionDescriptor) {
// adding libraries
for (OrderEntry e : ModuleRootManager.getInstance(myModule).getOrderEntries()) {
if (!(e instanceof LibraryOrderEntry)) continue;
LibraryOrderEntry loe = (LibraryOrderEntry) e;
if (!loe.isModuleLevel()) continue;
Library library = loe.getLibrary();
if (library == null) continue;
StubSolutionIdea.addModelRoots(solutionDescriptor, library.getFiles(OrderRootType.CLASSES));
}
}
public Module getIdeaModule() {
return myModule;
}
private class MyModuleRootListener implements ModuleRootListener {
@Override
public void beforeRootsChange(ModuleRootEvent event) {
}
@Override
public void rootsChanged(ModuleRootEvent event) {
if (myModule.getProject().equals(event.getSource())) {
reset();
}
}
}
private void reset() {
myModelAccess.runWriteAction(new Runnable() {
public void run() {
ApplicationManager.getApplication().getComponent(JdkStubSolutionManager.class).releaseSdk(myModule);
// this is to prevent a delayed write to be executed after the module has already been disposed
// TODO: find a better solution
if (myModule.isDisposed()) {
return;
}
setModuleDescriptor(getModuleDescriptor());
}
});
}
private class MyFacetManagerAdapter extends FacetManagerAdapter {
@Override
public void facetAdded(@NotNull Facet facet) {
handleFacetChanged(facet);
}
@Override
public void facetRemoved(@NotNull Facet facet) {
handleFacetChanged(facet);
}
}
private class MyRootSetChangedListener implements RootSetChangedListener {
@Override
public void rootSetChanged(final RootProvider wrapper) {
FindProcessor<OrderEntry> processor = new FindProcessor<OrderEntry>() {
@Override
protected boolean accept(OrderEntry orderEntry) {
return orderEntry instanceof LibraryOrderEntry && ((LibraryOrderEntry) orderEntry).getLibrary().getRootProvider() == wrapper
|| orderEntry instanceof JdkOrderEntry;
}
};
ModuleRootManager.getInstance(myModule).orderEntries().forEach(processor);
if (processor.isFound()) {
reset();
}
}
}
private class MyLibrariesListener implements Listener {
@Override
public void afterLibraryAdded(final Library newLibrary) {
if (ModuleLibraryType.isModuleLibrary(newLibrary)) {
newLibrary.getRootProvider().addRootSetChangedListener(myRootSetListener);
}
}
@Override
public void afterLibraryRenamed(Library library) {
}
@Override
public void beforeLibraryRemoved(Library library) {
library.getRootProvider().addRootSetChangedListener(myRootSetListener);
}
@Override
public void afterLibraryRemoved(Library library) {
}
}
@Override
public String toString() {
return getModuleName() + " [idea module derived solution]";
}
private final SModuleListener MODULE_RUNTIME_IMPORTER = new SModuleListenerBase() {
@Override
public void modelAdded(SModule module, SModel model) {
if (!(model instanceof SModelInternal)) return;
((SModelInternal) model).addModelListener(MODEL_RUNTIME_IMPORTER);
}
};
private final SModelListener MODEL_RUNTIME_IMPORTER = new SModelAdapter() {
@Override
public void languageAdded(SModelLanguageEvent event) {
SModuleReference langRef = event.getLanguageNamespace();
ModuleMPSSupport.getInstance().fixImports(myModule, Collections.singleton(langRef));
}
};
}