blob: e2a313ef30a9552a375cc9a655ac5a156117ebe9 [file] [log] [blame]
/*
* Copyright 2000-2013 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 org.jetbrains.idea.maven.importing;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.ModuleOrderEntryImpl;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.util.Processor;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.maven.model.MavenArtifact;
import org.jetbrains.idea.maven.model.MavenConstants;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.Path;
import org.jetbrains.idea.maven.utils.Url;
import org.jetbrains.jps.model.JpsElement;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.io.File;
import java.util.Arrays;
import java.util.Set;
public class MavenRootModelAdapter {
private final MavenProject myMavenProject;
private final ModifiableModuleModel myModuleModel;
private final ModifiableRootModel myRootModel;
private final MavenSourceFoldersModuleExtension myRootModelModuleExtension;
private final Set<String> myOrderEntriesBeforeJdk = new THashSet<String>();
public MavenRootModelAdapter(@NotNull MavenProject p, @NotNull Module module, final MavenModifiableModelsProvider rootModelsProvider) {
myMavenProject = p;
myModuleModel = rootModelsProvider.getModuleModel();
myRootModel = rootModelsProvider.getRootModel(module);
myRootModelModuleExtension = myRootModel.getModuleExtension(MavenSourceFoldersModuleExtension.class);
myRootModelModuleExtension.init(module, myRootModel);
}
public void init(boolean isNewlyCreatedModule) {
setupInitialValues(isNewlyCreatedModule);
initContentRoots();
initOrderEntries();
}
private void setupInitialValues(boolean newlyCreatedModule) {
if (newlyCreatedModule || myRootModel.getSdk() == null) {
myRootModel.inheritSdk();
}
if (newlyCreatedModule) {
getCompilerExtension().setExcludeOutput(true);
}
}
private void initContentRoots() {
Url url = toUrl(myMavenProject.getDirectory());
if (getContentRootFor(url) != null) return;
myRootModel.addContentEntry(url.getUrl());
}
private ContentEntry getContentRootFor(Url url) {
for (ContentEntry e : myRootModel.getContentEntries()) {
if (VfsUtilCore.isEqualOrAncestor(e.getUrl(), url.getUrl())) return e;
}
return null;
}
private void initOrderEntries() {
boolean jdkProcessed = false;
for (OrderEntry e : myRootModel.getOrderEntries()) {
if (e instanceof ModuleSourceOrderEntry || e instanceof JdkOrderEntry) {
jdkProcessed = true;
continue;
}
if (e instanceof LibraryOrderEntry) {
if (!isMavenLibrary(((LibraryOrderEntry)e).getLibrary())) continue;
}
if (e instanceof ModuleOrderEntry) {
Module m = ((ModuleOrderEntry)e).getModule();
if (m != null && !MavenProjectsManager.getInstance(myRootModel.getProject()).isMavenizedModule(m)) continue;
}
if (!jdkProcessed) {
if (e instanceof ModuleOrderEntry) {
myOrderEntriesBeforeJdk.add(((ModuleOrderEntry)e).getModuleName());
}
else if (e instanceof LibraryOrderEntry) {
myOrderEntriesBeforeJdk.add(((LibraryOrderEntry)e).getLibraryName());
}
}
myRootModel.removeOrderEntry(e);
}
}
public ModifiableRootModel getRootModel() {
return myRootModel;
}
@NotNull
public String[] getSourceRootUrls(boolean includingTests) {
return myRootModelModuleExtension.getSourceRootUrls(includingTests);
}
public Module getModule() {
return myRootModel.getModule();
}
public void clearSourceFolders() {
myRootModelModuleExtension.clearSourceFolders();
}
public <P extends JpsElement> void addSourceFolder(String path, final JpsModuleSourceRootType<P> rootType) {
addSourceFolder(path, rootType, false, rootType.createDefaultProperties());
}
public void addGeneratedJavaSourceFolder(String path, JavaSourceRootType rootType) {
addSourceFolder(path, rootType, true, JpsJavaExtensionService.getInstance().createSourceRootProperties("", true));
}
private <P extends JpsElement> void addSourceFolder(@NotNull String path, final @NotNull JpsModuleSourceRootType<P> rootType, boolean ifNotEmpty,
final @NotNull P properties) {
if (ifNotEmpty) {
String[] childs = new File(toPath(path).getPath()).list();
if (childs == null || childs.length == 0) return;
}
else {
if (!exists(path)) return;
}
Url url = toUrl(path);
myRootModelModuleExtension.addSourceFolder(url, rootType, properties);
}
public boolean hasRegisteredSourceSubfolder(@NotNull File f) {
String url = toUrl(f.getPath()).getUrl();
return myRootModelModuleExtension.hasRegisteredSourceSubfolder(url);
}
@Nullable
public SourceFolder getSourceFolder(File folder) {
String url = toUrl(folder.getPath()).getUrl();
return myRootModelModuleExtension.getSourceFolder(url);
}
public boolean isAlreadyExcluded(File f) {
String url = toUrl(f.getPath()).getUrl();
return VfsUtilCore.isUnder(url, Arrays.asList(myRootModel.getExcludeRootUrls()));
}
private boolean exists(String path) {
return new File(toPath(path).getPath()).exists();
}
public void addExcludedFolder(String path) {
unregisterAll(path, true, false);
Url url = toUrl(path);
ContentEntry e = getContentRootFor(url);
if (e == null) return;
if (e.getUrl().equals(url.getUrl())) return;
e.addExcludeFolder(url.getUrl());
if (!Registry.is("ide.hide.excluded.files")) {
Project project = myRootModel.getProject();
ChangeListManager.getInstance(project).addDirectoryToIgnoreImplicitly(toPath(path).getPath());
}
}
public void unregisterAll(String path, boolean under, boolean unregisterSources) {
Url url = toUrl(path);
for (ContentEntry eachEntry : myRootModel.getContentEntries()) {
if (unregisterSources) {
myRootModelModuleExtension.unregisterAll(url, under);
}
for (String excludedUrl : eachEntry.getExcludeFolderUrls()) {
String ancestor = under ? url.getUrl() : excludedUrl;
String child = under ? excludedUrl : url.getUrl();
if (VfsUtilCore.isEqualOrAncestor(ancestor, child)) {
eachEntry.removeExcludeFolder(excludedUrl);
}
}
for (String outputUrl : getCompilerExtension().getOutputRootUrls(true)) {
String ancestor = under ? url.getUrl() : outputUrl;
String child = under ? outputUrl : url.getUrl();
if (VfsUtilCore.isEqualOrAncestor(ancestor, child)) {
getCompilerExtension().setExcludeOutput(false);
}
}
}
}
public boolean hasCollision(String sourceRootPath) {
Url url = toUrl(sourceRootPath);
for (ContentEntry eachEntry : myRootModel.getContentEntries()) {
for (SourceFolder eachFolder : eachEntry.getSourceFolders()) {
String ancestor = url.getUrl();
String child = eachFolder.getUrl();
if (VfsUtilCore.isEqualOrAncestor(ancestor, child) || VfsUtilCore.isEqualOrAncestor(child, ancestor)) {
return true;
}
}
for (String excludeUrl : eachEntry.getExcludeFolderUrls()) {
String ancestor = url.getUrl();
if (VfsUtilCore.isEqualOrAncestor(ancestor, excludeUrl) || VfsUtilCore.isEqualOrAncestor(excludeUrl, ancestor)) {
return true;
}
}
}
return false;
}
public void useModuleOutput(String production, String test) {
getCompilerExtension().inheritCompilerOutputPath(false);
getCompilerExtension().setCompilerOutputPath(toUrl(production).getUrl());
getCompilerExtension().setCompilerOutputPathForTests(toUrl(test).getUrl());
}
private CompilerModuleExtension getCompilerExtension() {
return myRootModel.getModuleExtension(CompilerModuleExtension.class);
}
private Url toUrl(String path) {
return toPath(path).toUrl();
}
public Path toPath(String path) {
if (!FileUtil.isAbsolute(path)) {
path = new File(myMavenProject.getDirectory(), path).getPath();
}
return new Path(path);
}
public void addModuleDependency(@NotNull String moduleName,
@NotNull DependencyScope scope,
boolean testJar) {
Module m = findModuleByName(moduleName);
ModuleOrderEntry e;
if (m != null) {
e = myRootModel.addModuleOrderEntry(m);
}
else {
AccessToken accessToken = ReadAction.start();
try {
e = myRootModel.addInvalidModuleEntry(moduleName);
}
finally {
accessToken.finish();
}
}
e.setScope(scope);
if (testJar) {
((ModuleOrderEntryImpl)e).setProductionOnTestDependency(true);
}
if (myOrderEntriesBeforeJdk.contains(moduleName)) {
moveLastOrderEntryBeforeJdk();
}
}
@Nullable
public Module findModuleByName(String moduleName) {
return myModuleModel.findModuleByName(moduleName);
}
public void addSystemDependency(MavenArtifact artifact, DependencyScope scope) {
assert MavenConstants.SCOPE_SYSTEM.equals(artifact.getScope());
String libraryName = artifact.getLibraryName();
Library library = myRootModel.getModuleLibraryTable().getLibraryByName(libraryName);
if (library == null) {
library = myRootModel.getModuleLibraryTable().createLibrary(libraryName);
}
LibraryOrderEntry orderEntry = myRootModel.findLibraryOrderEntry(library);
assert orderEntry != null;
orderEntry.setScope(scope);
Library.ModifiableModel modifiableModel = library.getModifiableModel();
updateUrl(modifiableModel, OrderRootType.CLASSES, artifact, null, null, true);
modifiableModel.commit();
if (myOrderEntriesBeforeJdk.contains(libraryName)) {
moveLastOrderEntryBeforeJdk();
}
}
public void addLibraryDependency(MavenArtifact artifact,
DependencyScope scope,
MavenModifiableModelsProvider provider,
MavenProject project) {
assert !MavenConstants.SCOPE_SYSTEM.equals(artifact.getScope()); // System dependencies must be added ad module library, not as project wide library.
String libraryName = artifact.getLibraryName();
Library library = provider.getLibraryByName(libraryName);
if (library == null) {
library = provider.createLibrary(libraryName);
}
Library.ModifiableModel libraryModel = provider.getLibraryModel(library);
updateUrl(libraryModel, OrderRootType.CLASSES, artifact, null, null, true);
updateUrl(libraryModel, OrderRootType.SOURCES, artifact, MavenExtraArtifactType.SOURCES, project, false);
updateUrl(libraryModel, JavadocOrderRootType.getInstance(), artifact, MavenExtraArtifactType.DOCS, project, false);
LibraryOrderEntry e = myRootModel.addLibraryEntry(library);
e.setScope(scope);
if (myOrderEntriesBeforeJdk.contains(libraryName)) {
moveLastOrderEntryBeforeJdk();
}
}
private void moveLastOrderEntryBeforeJdk() {
OrderEntry[] entries = myRootModel.getOrderEntries().clone();
int i = entries.length - 1;
while (i > 0 && (entries[i - 1] instanceof ModuleSourceOrderEntry || entries[i - 1] instanceof JdkOrderEntry)) {
OrderEntry e = entries[i - 1];
entries[i - 1] = entries[i];
entries[i] = e;
i--;
}
if (i < entries.length) {
myRootModel.rearrangeOrderEntries(entries);
}
}
private static void updateUrl(Library.ModifiableModel library,
OrderRootType type,
MavenArtifact artifact,
MavenExtraArtifactType artifactType,
MavenProject project,
boolean clearAll) {
String classifier = null;
String extension = null;
if (artifactType != null) {
Pair<String, String> result = project.getClassifierAndExtension(artifact, artifactType);
classifier = result.first;
extension = result.second;
}
String newPath = artifact.getPathForExtraArtifact(classifier, extension);
String newUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, newPath) + JarFileSystem.JAR_SEPARATOR;
boolean urlExists = false;
for (String url : library.getUrls(type)) {
if (newUrl.equals(url)) {
urlExists = true;
continue;
}
if (clearAll || (isRepositoryUrl(artifact, url) && !url.startsWith(newUrl))) {
library.removeRoot(url, type);
}
}
if (!urlExists) {
library.addRoot(newUrl, type);
}
}
private static boolean isRepositoryUrl(MavenArtifact artifact, String url) {
return url.contains(artifact.getGroupId().replace('.', '/') + '/' + artifact.getArtifactId() + '/' + artifact.getBaseVersion() + '/' + artifact.getArtifactId() + '-');
}
public static boolean isChangedByUser(Library library) {
String[] classRoots = library.getUrls(OrderRootType.CLASSES);
if (classRoots.length != 1) return true;
String classes = classRoots[0];
if (!classes.endsWith("!/")) return true;
int dotPos = classes.lastIndexOf("/", classes.length() - 2 /* trim ending !/ */);
if (dotPos == -1) return true;
String pathToJar = classes.substring(0, dotPos);
if (hasUserPaths(OrderRootType.SOURCES, library, pathToJar)) return true;
if (hasUserPaths(JavadocOrderRootType.getInstance(), library, pathToJar)) return true;
return false;
}
private static boolean hasUserPaths(OrderRootType rootType, Library library, String pathToJar) {
String[] sources = library.getUrls(rootType);
for (String each : sources) {
if (!FileUtil.startsWith(each, pathToJar)) return true;
}
return false;
}
public Library findLibrary(@NotNull final MavenArtifact artifact) {
final String name = artifact.getLibraryName();
final Ref<Library> result = Ref.create(null);
myRootModel.orderEntries().forEachLibrary(new Processor<Library>() {
@Override
public boolean process(Library library) {
if (name.equals(library.getName())) {
result.set(library);
}
return true;
}
});
return result.get();
}
@Deprecated // Use artifact.getLibraryName();
public static String makeLibraryName(@NotNull MavenArtifact artifact) {
return artifact.getLibraryName();
}
public static boolean isMavenLibrary(@Nullable Library library) {
return library != null && MavenArtifact.isMavenLibrary(library.getName());
}
@Nullable
public static OrderEntry findLibraryEntry(@NotNull Module m, @NotNull MavenArtifact artifact) {
String name = artifact.getLibraryName();
for (OrderEntry each : ModuleRootManager.getInstance(m).getOrderEntries()) {
if (each instanceof LibraryOrderEntry && name.equals(((LibraryOrderEntry)each).getLibraryName())) {
return each;
}
}
return null;
}
@Nullable
public static MavenArtifact findArtifact(@NotNull MavenProject project, @Nullable Library library) {
if (library == null) return null;
String name = library.getName();
if (!MavenArtifact.isMavenLibrary(name)) return null;
for (MavenArtifact each : project.getDependencies()) {
if (each.getLibraryName().equals(name)) return each;
}
return null;
}
public void setLanguageLevel(LanguageLevel level) {
try {
myRootModel.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(level);
}
catch (IllegalArgumentException e) {
//bad value was stored
}
}
}