blob: b63e321b9877d8f68a8214d42fa6f0899d3eed0e [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 com.android.tools.idea.gradle.customizer;
import com.android.tools.idea.gradle.dependency.DependencySetupErrors;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import static com.android.SdkConstants.*;
import static com.android.tools.idea.gradle.util.Projects.getDependencySetupErrors;
import static com.android.tools.idea.gradle.util.Projects.setDependencySetupErrors;
import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName;
import static com.intellij.openapi.util.io.FileUtilRt.extensionEquals;
import static com.intellij.openapi.vfs.StandardFileSystems.FILE_PROTOCOL;
import static com.intellij.openapi.vfs.StandardFileSystems.JAR_PROTOCOL;
import static com.intellij.openapi.vfs.VirtualFileManager.constructUrl;
import static com.intellij.util.io.URLUtil.JAR_SEPARATOR;
import static java.io.File.separatorChar;
public abstract class AbstractDependenciesModuleCustomizer<T> implements ModuleCustomizer<T> {
@Override
public void customizeModule(@NotNull Project project, @NotNull ModifiableRootModel moduleModel, @Nullable T externalProjectModel) {
if (externalProjectModel == null) {
return;
}
removeExistingDependencies(moduleModel);
setUpDependencies(moduleModel, externalProjectModel);
}
protected abstract void setUpDependencies(@NotNull ModifiableRootModel rootModel, @NotNull T model);
@NotNull
protected DependencySetupErrors getSetupErrors(@NotNull Project project) {
DependencySetupErrors setupErrors = getDependencySetupErrors(project);
if (setupErrors == null) {
setupErrors = new DependencySetupErrors();
setDependencySetupErrors(project, setupErrors);
}
return setupErrors;
}
private static void removeExistingDependencies(@NotNull ModifiableRootModel model) {
DependencyRemover dependencyRemover = new DependencyRemover(model);
for (OrderEntry orderEntry : model.getOrderEntries()) {
orderEntry.accept(dependencyRemover, null);
}
}
protected static void setUpLibraryDependency(@NotNull ModifiableRootModel model,
@NotNull String libraryName,
@NotNull DependencyScope scope,
@NotNull Collection<String> binaryPaths) {
Collection<String> empty = Collections.emptyList();
setUpLibraryDependency(model, libraryName, scope, binaryPaths, empty, empty);
}
protected static void setUpLibraryDependency(@NotNull ModifiableRootModel model,
@NotNull String libraryName,
@NotNull DependencyScope scope,
@NotNull Collection<String> binaryPaths,
@NotNull Collection<String> sourcePaths,
@NotNull Collection<String> documentationPaths) {
LibraryTable libraryTable = ProjectLibraryTable.getInstance(model.getProject());
Library library = libraryTable.getLibraryByName(libraryName);
if (library == null) {
// Create library.
LibraryTable.ModifiableModel libraryTableModel = libraryTable.getModifiableModel();
try {
library = libraryTableModel.createLibrary(libraryName);
updateLibraryBinaryPaths(library, binaryPaths);
}
finally {
libraryTableModel.commit();
}
}
// It is common that the same dependency is used by more than one module. Here we update the "sources" and "documentation" paths if they
// were not set before.
// Example:
// In a multi-project project, there are 2 modules: 'app'' (an Android app) and 'util' (a Java lib.) Both of them depend on Guava. Since
// Android artifacts do not support source attachments, the 'app' module may not indicate where to find the sources for Guava, but the
// 'util' method can, since it is a plain Java module.
// If the 'Guava' library was already defined when setting up 'app', it won't have source attachments. When setting up 'util' we may
// have source attachments, but the library may have been already created. Here we just add the "source" paths if they were not already
// set.
updateLibrarySourcesIfAbsent(library, sourcePaths, OrderRootType.SOURCES);
updateLibrarySourcesIfAbsent(library, documentationPaths, JavadocOrderRootType.getInstance());
// Add external annotations.
// TODO: Add this to the model instead!
for (String binaryPath : binaryPaths) {
if (binaryPath.endsWith(FD_RES) && binaryPath.length() > FD_RES.length() &&
binaryPath.charAt(binaryPath.length() - FD_RES.length() - 1) == separatorChar) {
File annotations = new File(binaryPath.substring(0, binaryPath.length() - FD_RES.length()), FN_ANNOTATIONS_ZIP);
if (annotations.isFile()) {
updateLibrarySourcesIfAbsent(library, Collections.singletonList(annotations.getPath()), AnnotationOrderRootType.getInstance());
}
}
}
LibraryOrderEntry orderEntry = model.addLibraryEntry(library);
orderEntry.setScope(scope);
orderEntry.setExported(true);
}
private static void updateLibraryBinaryPaths(@NotNull Library library, @NotNull Collection<String> binaryPaths) {
Library.ModifiableModel libraryModel = library.getModifiableModel();
try {
for (String path : binaryPaths) {
String url = pathToUrl(path);
libraryModel.addRoot(url, OrderRootType.CLASSES);
}
}
finally {
libraryModel.commit();
}
}
private static void updateLibrarySourcesIfAbsent(@NotNull Library library,
@NotNull Collection<String> paths,
@NotNull OrderRootType pathType) {
if (paths.isEmpty() || library.getFiles(pathType).length > 0) {
return;
}
// We only update paths if the library does not have any already defined.
Library.ModifiableModel libraryModel = library.getModifiableModel();
try {
for (String path : paths) {
libraryModel.addRoot(pathToUrl(path), pathType);
}
}
finally {
libraryModel.commit();
}
}
@NotNull
public static String pathToUrl(@NotNull String path) {
File file = new File(path);
String name = file.getName();
boolean isJarFile = extensionEquals(name, EXT_JAR) || extensionEquals(name, EXT_ZIP);
// .jar files require an URL with "jar" protocol.
String protocol = isJarFile ? JAR_PROTOCOL : FILE_PROTOCOL;
String url = constructUrl(protocol, toSystemIndependentName(file.getPath()));
if (isJarFile) {
url += JAR_SEPARATOR;
}
return url;
}
private static class DependencyRemover extends RootPolicy<Object> {
@NotNull private final ModifiableRootModel myModel;
DependencyRemover(@NotNull ModifiableRootModel model) {
myModel = model;
}
@Override
public Object visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, Object value) {
myModel.removeOrderEntry(libraryOrderEntry);
return value;
}
@Override
public Object visitModuleOrderEntry(ModuleOrderEntry moduleOrderEntry, Object value) {
myModel.removeOrderEntry(moduleOrderEntry);
return value;
}
}
}