/*
* Copyright 2003-2012 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.stubs;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.ProjectJdkTable.Listener;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.projectRoots.SdkTypeId;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.messages.MessageBusConnection;
import jetbrains.mps.idea.core.MPSBundle;
import jetbrains.mps.util.ClassType;
import jetbrains.mps.extapi.module.SRepositoryExt;
import jetbrains.mps.ide.MPSCoreComponents;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.project.StubSolutionIdea;
import jetbrains.mps.project.ModuleId;
import jetbrains.mps.project.Solution;
import jetbrains.mps.project.structure.model.ModelRootDescriptor;
import jetbrains.mps.project.structure.modules.ModuleDescriptor;
import jetbrains.mps.reloading.CommonPaths;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* User: shatalin
* Date: 4/27/12
*/
public class JdkStubSolutionManager extends AbstractJavaStubSolutionManager implements ApplicationComponent, Listener {
private final Object LOCK = new Object();
private static final String JAVA_SDK_TYPE = "JavaSDK";
private static final String IDEA_SDK_TYPE = "IDEA JDK";
private ProjectJdkTable myTable;
private MessageBusConnection myListenerConnection;
// idea modules that need stubs for their java or idea SDKs
// (only jdk or idea sdk, since we track them specifically)
private Set<Module> myModules = new HashSet<Module>();
private Sdk myJavaSdk;
private Solution myJavaSdkSolution;
private Sdk myIdeaSdk;
private Solution myIdeaSdkSolution;
@Override
public boolean isHidden() {
return false;
}
@SuppressWarnings("UnusedParameters")
public JdkStubSolutionManager(MPSCoreComponents core, ProjectJdkTable table) {
myTable = table;
}
public Solution getModuleSdkSolution(Module module) {
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk == null) return null;
return getSdkSolution(sdk, ProjectHelper.getProjectRepository(module.getProject()));
}
public Solution getSdkSolution(final Sdk sdk, SRepository repository) {
assert repository.getModelAccess().canRead();
synchronized (LOCK) {
if (sdk.equals(myJavaSdk)) return myJavaSdkSolution;
if (sdk.equals(myIdeaSdk)) return myIdeaSdkSolution;
}
// otherwise normal logic: by foreign id
return new ModelAccessHelper(repository.getModelAccess()).runReadAction(new Computable<Solution>() {
@Override
public Solution compute() {
return (Solution) repository.getModule(ModuleId.foreign(sdk.getName()));
}
});
}
public Collection<Module> getModules() {
return Collections.unmodifiableCollection(myModules);
}
public void claimSdk(Module module) throws DifferentSdkException {
SRepository repository = ProjectHelper.getProjectRepository(module.getProject());
assert repository.getModelAccess().canWrite();
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
// ?
if (!(sdk instanceof SdkModificator)) {
return;
}
String sdkType = sdk.getSdkType().getName();
synchronized (LOCK) {
if (JAVA_SDK_TYPE.equals(sdkType)) {
// it's JDK
if (sdk.equals(myJavaSdk)) return; // nothing to do
dropSdksIfUnused((SRepositoryExt) repository);
if (myJavaSdk == null) {
// either no sdks at all, or only idea sdk without underlying jdk: we're free to set it up
setUpJdk(sdk, repository);
myModules.add(module);
return;
}
// we don't support multiple JDKs for now
throw new DifferentSdkException(myJavaSdk, sdk);
} else if (IDEA_SDK_TYPE.equals(sdkType)) {
// it's an IDEA SDK, we treat it specially cause we know it has jdk as its base
if (sdk.equals(myIdeaSdk)) return; // do nothing
dropSdksIfUnused((SRepositoryExt) repository);
if (myIdeaSdk == null) {
setUpIdeaSdk(sdk, repository);
myModules.add(module);
return;
}
// we don't support multiple Idea SDKs for now
throw new DifferentSdkException(myIdeaSdk, sdk);
} else {
// TODO pull out jdk that can be (or must be?) beneath this sdk
addSolution(sdk, (SRepositoryExt) repository);
}
}
}
public void releaseSdk(Module module) {
SRepository repository = ProjectHelper.getProjectRepository(module.getProject());
assert repository.getModelAccess().canWrite();
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk == null) return;
synchronized (LOCK) {
if (sdk.equals(myJavaSdk) || sdk.equals(myIdeaSdk)) {
myModules.remove(module);
}
}
}
@Override
protected void handleModuleNameTaken(StubModuleNameTakenException exception) {
String message = String.format(
MPSBundle.message("mps.stub.warning.duplicate.sdk.message"),
exception.getLibraryName(),
exception.getNamespace());
new Notification(
MPSBundle.message("mps.stub.warning.group.display.id"),
MPSBundle.message("mps.stub.warning.duplicate.sdk.title"),
message,
NotificationType.WARNING).notify();
}
/**
*
* @param repository A repository. It's implied to be simply an accessor to global repository, a means
* to take a lock. Because our SDKs are currently tracked per application, not per project;
*/
private void dropSdksIfUnused(SRepositoryExt repository) {
// if no modules are left, we can throw our sdk solutions away and set up another one
if (myModules.isEmpty()) {
if (myJavaSdkSolution != null) {
repository.unregisterModule(myJavaSdkSolution, JdkStubSolutionManager.this);
myJavaSdk = null;
myJavaSdkSolution = null;
}
if (myIdeaSdkSolution != null) {
repository.unregisterModule(myIdeaSdkSolution, JdkStubSolutionManager.this);
myIdeaSdk = null;
myIdeaSdkSolution = null;
}
}
}
private void setUpJdk(Sdk sdk, SRepository repository) {
myJavaSdkSolution = replaceJdkSolution(sdk, (SRepositoryExt) repository);
myJavaSdk = sdk;
}
private void setUpIdeaSdk(Sdk sdk, SRepository repository) throws DifferentSdkException {
// first we check that this idea sdk uses the right jdk
Sdk jdk = guessJdk(sdk);
// jdk can be null if user has specifically removed jdk jars from idea sdk
if (jdk != null) {
if (myJavaSdk != null && !myJavaSdk.equals(jdk)) {
// TODO specify that idea sdk didn't match jdk, not just difference
throw new DifferentSdkException(myJavaSdk, jdk);
}
if (myJavaSdk == null) setUpJdk(jdk, repository);
}
// we exclude jdk roots (if jdk used)
Set<VirtualFile> jdkRoots = new HashSet<VirtualFile>();
if (jdk != null) {
Collections.addAll(jdkRoots, jdk.getRootProvider().getFiles(OrderRootType.CLASSES));
}
// we exclude jars that are in MPS.Platform, they stay there
List<String> excludedPaths = new ArrayList<String>();
excludedPaths.addAll(CommonPaths.getMPSPaths(ClassType.PLATFORM));
excludedPaths.addAll(CommonPaths.getMPSPaths(ClassType.CORE));
// turn into short names
for (int i = 0; i < excludedPaths.size(); i++) {
String path = excludedPaths.get(i);
// using io.File, same as in CommonPaths
String shortName = new File(path).getName();
excludedPaths.set(i, shortName);
}
Set<String> excludedFiles = new HashSet<String>(excludedPaths);
// make the list of needed roots
VirtualFile[] allRoots = sdk.getRootProvider().getFiles(OrderRootType.CLASSES);
List<VirtualFile> remainingRoots = new ArrayList<VirtualFile>();
for (VirtualFile file : allRoots) {
if (jdkRoots.contains(file)) continue;
if (excludedFiles.contains(file.getName())) continue;
remainingRoots.add(file);
}
VirtualFile[] roots = remainingRoots.toArray(new VirtualFile[0]);
// remove from MPS.Platform 2 jars that contain idea classes but have different names,
// not like in idea sdk
SModule mpsPlatform = repository.getModule(ModuleId.regular(UUID.fromString("742f6602-5a2f-4313-aa6e-ae1cd4ffdc61")));
assert mpsPlatform instanceof Solution;
Set<String> ideaStuffPaths = new HashSet<String>(CommonPaths.getMPSPaths(ClassType.IDEA_PLATFORM));
ModuleDescriptor mpsPlatfromDesc = ((Solution) mpsPlatform).getModuleDescriptor();
Iterator<ModelRootDescriptor> platformModelRoots = mpsPlatfromDesc.getModelRootDescriptors().iterator();
boolean changed = false;
while (platformModelRoots.hasNext()) {
ModelRootDescriptor modelRoot = platformModelRoots.next();
if (ideaStuffPaths.contains(modelRoot.getMemento().get("path"))) {
platformModelRoots.remove();
changed = true;
}
}
if (changed) {
((Solution) mpsPlatform).setUpdateBootstrapSolutions(false);
((Solution) mpsPlatform).setModuleDescriptor(mpsPlatfromDesc);
}
myIdeaSdkSolution = StubSolutionIdea.newInstanceForRoots(sdk, jdk, roots, this, (SRepositoryExt) repository);
myIdeaSdk = sdk;
}
private Sdk guessJdk(Sdk sdk) {
VirtualFile[] roots = sdk.getRootProvider().getFiles(OrderRootType.CLASSES);
SdkTypeId jdkTypeId = JavaSdk.getInstance();
for (Sdk jdk : myTable.getSdksOfType(jdkTypeId)) {
String homePath = jdk.getHomePath();
for (VirtualFile root : roots) {
if (root.getPath().startsWith(homePath)) return jdk;
}
}
return null;
}
@Override
protected void init() {
}
@Override
protected void dispose() {
super.dispose();
// todo remove listener
};
@Override
public void jdkAdded(final Sdk jdk) {
}
@Override
public void jdkRemoved(final Sdk jdk) {
}
@Override
public void jdkNameChanged(Sdk jdk, String previousName) {
//todo update models
}
}