/*
* 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.messages;
import com.android.ide.common.repository.GradleCoordinate;
import com.android.ide.common.repository.SdkMavenRepository;
import com.android.sdklib.repository.NoPreviewRevision;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.sdklib.repository.descriptors.IdDisplay;
import com.android.sdklib.repository.descriptors.PkgDesc;
import com.android.tools.idea.gradle.IdeaGradleProject;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.project.GradleProjectImporter;
import com.android.tools.idea.gradle.service.notification.GradleNotificationExtension;
import com.android.tools.idea.gradle.service.notification.errors.AbstractSyncErrorHandler;
import com.android.tools.idea.gradle.service.notification.hyperlink.NotificationHyperlink;
import com.android.tools.idea.gradle.service.notification.hyperlink.OpenFileHyperlink;
import com.android.tools.idea.gradle.structure.AndroidProjectSettingsService;
import com.android.tools.idea.gradle.util.Projects;
import com.android.tools.idea.sdk.DefaultSdks;
import com.android.tools.idea.sdk.wizard.SdkQuickfixWizard;
import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
import com.google.common.collect.Lists;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager;
import com.intellij.openapi.externalSystem.service.notification.NotificationCategory;
import com.intellij.openapi.externalSystem.service.notification.NotificationData;
import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.util.GradleConstants;
import java.io.File;
import java.util.Collection;
import java.util.List;
import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.UNRESOLVED_ANDROID_DEPENDENCIES;
import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.UNRESOLVED_DEPENDENCIES;
/**
* Service that collects and displays, in the "Messages" tool window, post-sync project setup messages (errors, warnings, etc.)
*/
public class ProjectSyncMessages {
@NotNull private final Project myProject;
@NotNull private final ExternalSystemNotificationManager myNotificationManager;
@NotNull
public static ProjectSyncMessages getInstance(@NotNull Project project) {
return ServiceManager.getService(project, ProjectSyncMessages.class);
}
public int getErrorCount() {
return myNotificationManager
.getMessageCount(null, NotificationSource.PROJECT_SYNC, NotificationCategory.ERROR, GradleConstants.SYSTEM_ID);
}
public ProjectSyncMessages(@NotNull Project project, @NotNull ExternalSystemNotificationManager manager) {
myProject = project;
myNotificationManager = manager;
}
public void removeMessages(@NotNull String groupName) {
myNotificationManager.clearNotifications(groupName, NotificationSource.PROJECT_SYNC, GradleConstants.SYSTEM_ID);
}
public int getMessageCount(@NotNull String groupName) {
return myNotificationManager.getMessageCount(groupName, NotificationSource.PROJECT_SYNC, null, GradleConstants.SYSTEM_ID);
}
public void add(@NotNull final Message message, @NotNull NotificationHyperlink... hyperlinks) {
Navigatable navigatable = message.getNavigatable();
String title = message.getGroupName();
String errorMsg = StringUtil.join(message.getText(), "\n");
VirtualFile file = message.getFile();
String filePath = file != null ? FileUtil.toSystemDependentName(file.getPath()) : null;
NotificationData notification =
new NotificationData(title, errorMsg, NotificationCategory.convert(message.getType().getValue()), NotificationSource.PROJECT_SYNC,
filePath, message.getLine(), message.getColumn(), false);
notification.setNavigatable(navigatable);
if (hyperlinks.length > 0) {
AbstractSyncErrorHandler.updateNotification(notification, myProject, title, errorMsg, hyperlinks);
}
myNotificationManager.showNotification(GradleConstants.SYSTEM_ID, notification);
}
public boolean isEmpty() {
return myNotificationManager.getMessageCount(NotificationSource.PROJECT_SYNC, null, GradleConstants.SYSTEM_ID) == 0;
}
public void reportUnresolvedDependencies(@NotNull Collection<String> unresolvedDependencies, @NotNull Module module) {
VirtualFile buildFile = null;
AndroidGradleFacet gradleFacet = AndroidGradleFacet.getInstance(module);
if (gradleFacet != null && gradleFacet.getGradleProject() != null) {
IdeaGradleProject gradleProject = gradleFacet.getGradleProject();
buildFile = gradleProject.getBuildFile();
}
for (String dep : unresolvedDependencies) {
List<NotificationHyperlink> hyperlinks = Lists.newArrayList();
File androidHome = getAndroidHome(module);
String group;
if (dep.startsWith("com.android.support")) {
group = UNRESOLVED_ANDROID_DEPENDENCIES;
File repository = SdkMavenRepository.ANDROID.getRepositoryLocation(androidHome, true);
if (repository != null) {
hyperlinks.add(InstallRepositoryHyperlink.installAndroidRepository());
}
}
else if (dep.startsWith("com.google.android.gms")) {
group = UNRESOLVED_ANDROID_DEPENDENCIES;
File repository = SdkMavenRepository.GOOGLE.getRepositoryLocation(androidHome, true);
if (repository != null) {
hyperlinks.add(InstallRepositoryHyperlink.installGoogleRepository());
}
}
else {
group = UNRESOLVED_DEPENDENCIES;
}
String text = "Failed to find: " + dep;
Message msg;
if (buildFile != null) {
msg = new Message(module.getProject(), group, Message.Type.ERROR, buildFile, -1, -1, text);
hyperlinks.add(new OpenFileHyperlink(buildFile.getPath()));
}
else {
msg = new Message(group, Message.Type.ERROR, AbstractNavigatable.NOT_NAVIGATABLE, text);
}
if (AndroidStudioSpecificInitializer.isAndroidStudio()) {
GradleCoordinate coordinate = GradleCoordinate.parseCoordinateString(dep);
if (coordinate != null) {
hyperlinks.add(new OpenDependencyInProjectStructureHyperlink(module, coordinate));
}
}
add(msg, hyperlinks.toArray(new NotificationHyperlink[hyperlinks.size()]));
}
if (!unresolvedDependencies.isEmpty()) {
myProject.putUserData(Projects.HAS_UNRESOLVED_DEPENDENCIES, true);
}
}
@Nullable
private static File getAndroidHome(@NotNull Module module) {
if (AndroidStudioSpecificInitializer.isAndroidStudio()) {
return DefaultSdks.getDefaultAndroidHome();
}
else {
// TODO test this in IntelliJ
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk != null && sdk.getHomePath() != null) {
return new File(FileUtil.toSystemDependentName(sdk.getHomePath()));
}
}
return null;
}
private static class OpenDependencyInProjectStructureHyperlink extends NotificationHyperlink {
@NotNull private final Module myModule;
@NotNull private final GradleCoordinate myDependency;
OpenDependencyInProjectStructureHyperlink(@NotNull Module module, @NotNull GradleCoordinate dependency) {
super("open.dependency.in.project.structure", "Open in Project Structure dialog");
myModule = module;
myDependency = dependency;
}
@Override
protected void execute(@NotNull Project project) {
ProjectSettingsService service = ProjectSettingsService.getInstance(project);
if (service instanceof AndroidProjectSettingsService) {
((AndroidProjectSettingsService)service).openAndSelectDependency(myModule, myDependency);
}
}
}
private static class InstallRepositoryHyperlink extends NotificationHyperlink {
@NotNull private final IdDisplay myIdDisplay;
@NotNull private final String myPath;
@NotNull private final String myDisplayName;
@NotNull
static InstallRepositoryHyperlink installAndroidRepository() {
return new InstallRepositoryHyperlink(new IdDisplay("android", "Android"), "m2repository", "Android Support Repository");
}
@NotNull
static InstallRepositoryHyperlink installGoogleRepository() {
return new InstallRepositoryHyperlink(new IdDisplay("google", "Google"), "m2repository", "Google Support Repository");
}
InstallRepositoryHyperlink(@NotNull IdDisplay idDisplay, @NotNull String path, @NotNull String displayName) {
super("install.m2.repo", "Install Repository and sync project");
myIdDisplay = idDisplay;
myPath = path;
myDisplayName = displayName;
}
@Override
protected void execute(@NotNull Project project) {
List<IPkgDesc> requested = Lists.newArrayList();
requested.add(PkgDesc.Builder.newExtra(myIdDisplay, myPath, myDisplayName, null, new NoPreviewRevision(1)).create());
SdkQuickfixWizard wizard = new SdkQuickfixWizard(project, null, requested);
wizard.init();
if (wizard.showAndGet()) {
GradleProjectImporter.getInstance().requestProjectSync(project, null);
}
}
}
}