/*
* Copyright 2003-2015 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.notification.Notification;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.RootProvider;
import com.intellij.openapi.roots.RootProvider.RootSetChangedListener;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.vfs.VirtualFile;
import jetbrains.mps.classloading.IdeaPluginModuleFacet;
import jetbrains.mps.extapi.module.SRepositoryExt;
import jetbrains.mps.extapi.persistence.FileBasedModelRoot;
import jetbrains.mps.idea.core.project.stubs.JdkStubSolutionManager;
import jetbrains.mps.idea.core.project.stubs.StubModuleNameTakenException;
import jetbrains.mps.module.SDependencyImpl;
import jetbrains.mps.persistence.MementoImpl;
import jetbrains.mps.persistence.PersistenceRegistry;
import jetbrains.mps.persistence.java.library.JavaClassStubsModelRoot;
import jetbrains.mps.project.ModuleId;
import jetbrains.mps.project.Solution;
import jetbrains.mps.project.StubSolution;
import jetbrains.mps.project.structure.model.ModelRootDescriptor;
import jetbrains.mps.project.structure.modules.ModuleFacetDescriptor;
import jetbrains.mps.project.structure.modules.SolutionDescriptor;
import jetbrains.mps.smodel.BootstrapLanguages;
import jetbrains.mps.smodel.MPSModuleOwner;
import jetbrains.mps.smodel.ModuleRepositoryFacade;
import jetbrains.mps.vfs.FileSystem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.persistence.ModelRoot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class StubSolutionIdea extends StubSolution {
private ModelAccess myModelAccess;
private final RootSetChangedListener myRootSetChangedListener = new RootSetChangedListener() {
@Override
public void rootSetChanged(RootProvider wrapper) {
myModelAccess.runWriteAction(new Runnable() {
@Override
public void run() {
SolutionDescriptor moduleDescriptor = getModuleDescriptor();
moduleDescriptor.getModelRootDescriptors().clear();
addModelRoots(moduleDescriptor, getRootProvider().getFiles(OrderRootType.CLASSES));
setModuleDescriptor(moduleDescriptor);
}
});
}
};
protected StubSolutionIdea(SolutionDescriptor descriptor, ModelAccess modelAccess) {
super(descriptor, null);
myModelAccess = modelAccess;
}
@Nullable
public static Solution newInstance(Library library, MPSModuleOwner moduleOwner, SRepositoryExt repository) throws StubModuleNameTakenException {
String namespace = library.getName();
if (namespace != null && new ModuleRepositoryFacade(repository).getModuleByName(namespace) != null) {
throw new StubModuleNameTakenException(library.getName(), namespace);
}
SolutionDescriptor descriptor = createDescriptor(namespace, library.getFiles(OrderRootType.CLASSES));
return register(repository, moduleOwner, new LibraryStubSolution(descriptor, library, repository.getModelAccess()));
}
public static Solution newInstance(Sdk sdk, Sdk baseJdk, MPSModuleOwner moduleOwner, SRepositoryExt repository) {
SolutionDescriptor descriptor = createDescriptor(sdk.getName(), ((SdkModificator) sdk).getRoots(OrderRootType.CLASSES));
return register(repository, moduleOwner, new SdkStubSolution(descriptor, sdk, baseJdk, repository.getModelAccess()));
}
public static Solution newInstanceForJdk(Sdk sdk, MPSModuleOwner moduleOwner, SRepositoryExt repository) {
SolutionDescriptor descriptor = createDescriptor("JDK", ((SdkModificator) sdk).getRoots(OrderRootType.CLASSES));
// giving the SDK the hard-coded module id
ModuleId jdkId = ModuleId.regular(UUID.fromString("6354ebe7-c22a-4a0f-ac54-50b52ab9b065"));
SModule jdkModule = repository.getModule(jdkId);
if (jdkModule != null) {
Set<MPSModuleOwner> owners = new HashSet<MPSModuleOwner>(new ModuleRepositoryFacade(repository).getModuleOwners(jdkModule));
for (MPSModuleOwner owner : owners) {
// FIXME unregister leads to warnings in ModuleUpdater.updateAllEdges()
// we register it back in the same write action but listener has the time to see the bad state:
// JDK module is missing and a lot depends on it
repository.unregisterModule(jdkModule, owner);
}
}
descriptor.setId(jdkId);
return register(repository, moduleOwner, new SdkStubSolution(descriptor, sdk, null, repository.getModelAccess()));
}
public static Solution newInstanceForRoots(Sdk sdk, Sdk baseJdk, VirtualFile[] roots, MPSModuleOwner moduleOwner, SRepositoryExt repository) {
SolutionDescriptor descriptor = createDescriptor(sdk.getName(), roots);
return register(repository, moduleOwner, new SdkStubSolution(descriptor, sdk, baseJdk, repository.getModelAccess()));
}
@Nullable
public static Library findLibrary(StubSolutionIdea solutionIdea) {
if (solutionIdea instanceof LibraryStubSolution) {
return ((LibraryStubSolution) solutionIdea).getLibrary();
}
// sdk?
return null;
}
private static SolutionDescriptor createDescriptor(String name, VirtualFile[] roots) {
SolutionDescriptor sd = new SolutionDescriptor();
sd.setNamespace(name);
sd.setId(ModuleId.foreign(name));
sd.setCompileInMPS(false);
sd.getModuleFacetDescriptors().add(new ModuleFacetDescriptor(IdeaPluginModuleFacet.FACET_TYPE, new MementoImpl()));
addModelRoots(sd, roots);
return sd;
}
protected void attachRootsListener() {
getRootProvider().addRootSetChangedListener(myRootSetChangedListener);
}
protected abstract RootProvider getRootProvider();
public static List<ModelRoot> getModelRoots(VirtualFile[] roots) {
List<ModelRoot> result = new ArrayList<ModelRoot>();
for (VirtualFile f : roots) {
String localPath = getLocalPath(f);
JavaClassStubsModelRoot modelRoot = new JavaClassStubsModelRoot();
modelRoot.setContentRoot(localPath);
modelRoot.addFile(FileBasedModelRoot.SOURCE_ROOTS, localPath);
result.add(modelRoot);
}
return result;
}
public static void addModelRoots(SolutionDescriptor solutionDescriptor, VirtualFile[] roots) {
Set<String> seenPaths = new LinkedHashSet<String>();
for (ModelRootDescriptor d : solutionDescriptor.getModelRootDescriptors()) {
if (!PersistenceRegistry.JAVA_CLASSES_ROOT.equals(d.getType())) continue;
seenPaths.add(d.getMemento().get("path"));
}
for (VirtualFile f : roots) {
String localPath = getLocalPath(f);
if (!seenPaths.add(localPath)) continue;
solutionDescriptor.getModelRootDescriptors().add(ModelRootDescriptor.getJavaStubsModelRoot(FileSystem.getInstance().getFile(localPath)));
}
}
private static String getLocalPath(VirtualFile f) {
String path = f.getPath();
int index = path.indexOf("!");
if (index < 0) return path;
return path.substring(0, index);
}
@Override
public void dispose() {
getRootProvider().removeRootSetChangedListener(myRootSetChangedListener);
super.dispose();
}
@Override
public Iterable<SDependency> getDeclaredDependencies() {
Set<SDependency> deps = new HashSet<SDependency>();
// explicitly add jdk
// todo: remove when sdk are loaded correctly
// Solution jdkSolutionReference = getJdkSolution();
// if (jdkSolutionReference != null) {
// modules.add(jdkSolutionReference);
// }
for (SModule module : ModuleRepositoryFacade.getInstance().getAllModules(StubSolutionIdea.class)) {
deps.add(new SDependencyImpl(module, SDependencyScope.DEFAULT, false));
}
return deps;
}
@Nullable
public static Solution getJdkSolution() {
return ModuleRepositoryFacade.getInstance().getModule(BootstrapLanguages.jdkRef(), Solution.class);
}
private static class LibraryStubSolution extends StubSolutionIdea {
@NotNull
private final Library myLibrary;
protected LibraryStubSolution(SolutionDescriptor descriptor, @NotNull Library library, ModelAccess modelAccess) {
super(descriptor, modelAccess);
myLibrary = library;
attachRootsListener();
// todo handle rename; no idea how
}
@Override
protected RootProvider getRootProvider() {
return myLibrary.getRootProvider();
}
@NotNull
public Library getLibrary() {
return myLibrary;
}
}
private static class SdkStubSolution extends StubSolutionIdea {
@NotNull
private final Sdk mySdk;
private final Sdk myBaseJdk;
protected SdkStubSolution(SolutionDescriptor descriptor, @NotNull Sdk sdk, Sdk baseJdk, ModelAccess modelAccess) {
super(descriptor, modelAccess);
mySdk = sdk;
myBaseJdk = baseJdk;
setUpdateBootstrapSolutions(false);
attachRootsListener();
}
@Override
protected RootProvider getRootProvider() {
return mySdk.getRootProvider();
}
@Override
public Iterable<SDependency> getDeclaredDependencies() {
if (myBaseJdk == null) return Collections.emptySet();
Solution baseJdkSolution = ApplicationManager.getApplication().getComponent(JdkStubSolutionManager.class).getSdkSolution(myBaseJdk, getRepository());
return Collections.<SDependency>singleton(new SDependencyImpl(baseJdkSolution, SDependencyScope.DEFAULT, true));
}
}
@Override
public String toString() {
return getModuleName() + " [idea stub solution]";
}
}