Java tutorial
/* * 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.project; import com.android.SdkConstants; import com.android.sdklib.repository.FullRevision; import com.android.tools.idea.gradle.messages.ProjectSyncMessages; import com.android.tools.idea.gradle.util.GradleProperties; import com.intellij.CommonBundle; import com.intellij.ide.util.PropertiesComponent; import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper.DoNotAskOption; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.net.HttpConfigurable; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.settings.DistributionType; import org.jetbrains.plugins.gradle.settings.GradleProjectSettings; import java.io.File; import java.io.IOException; import static com.android.tools.idea.gradle.util.GradleUtil.*; import static com.android.tools.idea.gradle.util.Projects.getBaseDirPath; import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.isAndroidStudio; import static com.intellij.openapi.ui.Messages.*; import static com.intellij.openapi.util.io.FileUtil.delete; import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName; import static com.intellij.openapi.util.text.StringUtil.isEmpty; import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; import static com.intellij.util.ExceptionUtil.getRootCause; import static org.jetbrains.plugins.gradle.settings.DistributionType.DEFAULT_WRAPPED; import static org.jetbrains.plugins.gradle.settings.DistributionType.LOCAL; final class PreSyncChecks { private static final Logger LOG = Logger.getInstance(PreSyncChecks.class); private static final String GRADLE_SYNC_MSG_TITLE = "Gradle Sync"; private static final String PROJECT_SYNCING_ERROR_GROUP = "Project syncing error"; @NonNls private static final String SHOW_DO_NOT_ASK_TO_COPY_PROXY_SETTINGS_PROPERTY_NAME = "show.do.not.copy.http.proxy.settings.to.gradle"; private PreSyncChecks() { } @NotNull static PreSyncCheckResult canSync(@NotNull Project project) { VirtualFile baseDir = project.getBaseDir(); if (baseDir == null) { // Unlikely to happen because it would mean this is the default project. return PreSyncCheckResult.success(); } if (isAndroidStudio()) { // We only check proxy settings for Studio, because Studio does not pass the IDE's proxy settings to Gradle. // See https://code.google.com/p/android/issues/detail?id=169743 checkHttpProxySettings(project); } ProjectSyncMessages syncMessages = ProjectSyncMessages.getInstance(project); syncMessages.removeMessages(PROJECT_SYNCING_ERROR_GROUP); if (!ApplicationManager.getApplication().isUnitTestMode()) { attemptToUseEmbeddedGradle(project); } GradleProjectSettings gradleSettings = getGradleProjectSettings(project); File wrapperPropertiesFile = findWrapperPropertiesFile(project); DistributionType distributionType = gradleSettings != null ? gradleSettings.getDistributionType() : null; boolean usingWrapper = (distributionType == null || distributionType == DEFAULT_WRAPPED) && wrapperPropertiesFile != null; if (usingWrapper && gradleSettings != null) { // Do this just to ensure that the right distribution type is set. If this is not set, build.gradle editor will not have code // completion (see BuildClasspathModuleGradleDataService, line 119). gradleSettings.setDistributionType(DEFAULT_WRAPPED); } else if (!ApplicationManager.getApplication().isUnitTestMode()) { if (wrapperPropertiesFile == null && gradleSettings != null) { createWrapperIfNecessary(project, gradleSettings, distributionType); } } return PreSyncCheckResult.success(); } // If the IDE is configured to use proxies, we ask the user if she would like to have those settings copied to gradle.properties, if such // files does not include them already. // Gradle may need those settings to access the Internet to download dependencies. // See https://code.google.com/p/android/issues/detail?id=65325 private static void checkHttpProxySettings(@NotNull Project project) { boolean performCheck = PropertiesComponent.getInstance() .getBoolean(SHOW_DO_NOT_ASK_TO_COPY_PROXY_SETTINGS_PROPERTY_NAME, true); if (!performCheck) { // User already checked the "do not ask me" option. return; } HttpConfigurable ideProxySettings = HttpConfigurable.getInstance(); if (ideProxySettings.USE_HTTP_PROXY && isNotEmpty(ideProxySettings.PROXY_HOST)) { GradleProperties properties; try { properties = new GradleProperties(project); } catch (IOException e) { LOG.info("Failed to read gradle.properties file", e); // Let sync continue, even though it may fail. return; } GradleProperties.ProxySettings proxySettings = properties.getProxySettings(); if (!ideProxySettings.PROXY_HOST.equals(proxySettings.getHost())) { String msg = "Android Studio is configured to use a HTTP proxy. " + "Gradle may need these proxy settings to access the Internet (e.g. for downloading dependencies.)\n\n" + "Would you like to have the IDE's proxy configuration be set in the project's gradle.properties file?"; DoNotAskOption doNotAskOption = new PropertyDoNotAskOption( SHOW_DO_NOT_ASK_TO_COPY_PROXY_SETTINGS_PROPERTY_NAME); int result = Messages.showYesNoDialog(project, msg, "Proxy Settings", Messages.getQuestionIcon(), doNotAskOption); if (result == YES) { proxySettings.copyFrom(ideProxySettings); properties.setProxySettings(proxySettings); try { properties.save(); } catch (IOException e) { //noinspection ThrowableResultOfMethodCallIgnored Throwable root = getRootCause(e); String cause = root.getMessage(); String errMsg = "Failed to save changes to gradle.properties file."; if (isNotEmpty(cause)) { if (!cause.endsWith(".")) { cause += "."; } errMsg += String.format("\nCause: %1$s", cause); } AndroidGradleNotification notification = AndroidGradleNotification.getInstance(project); notification.showBalloon("Proxy Settings", errMsg, NotificationType.ERROR); LOG.info("Failed to save changes to gradle.properties file", e); } } } } } private static boolean createWrapperIfNecessary(@NotNull Project project, @NotNull GradleProjectSettings gradleSettings, @Nullable DistributionType distributionType) { boolean createWrapper = false; boolean chooseLocalGradleHome = false; if (distributionType == null) { String msg = createUseWrapperQuestion("Gradle settings for this project are not configured yet."); int answer = showOkCancelDialog(project, msg, GRADLE_SYNC_MSG_TITLE, getQuestionIcon()); createWrapper = answer == Messages.OK; } else if (distributionType == DEFAULT_WRAPPED) { createWrapper = true; } else if (distributionType == LOCAL) { String gradleHome = gradleSettings.getGradleHome(); String msg = null; if (isEmpty(gradleHome)) { msg = createUseWrapperQuestion("The path of the local Gradle distribution to use is not set."); } else { File gradleHomePath = new File(toSystemDependentName(gradleHome)); if (!gradleHomePath.isDirectory()) { String reason = String.format( "The path\n'%1$s'\n, set as a local Gradle distribution, does not belong to an existing directory.", gradleHomePath.getPath()); msg = createUseWrapperQuestion(reason); } else { FullRevision gradleVersion = getGradleVersion(gradleHomePath); if (gradleVersion == null) { String reason = String.format( "The path\n'%1$s'\n, does not belong to a Gradle distribution.", gradleHomePath.getPath()); msg = createUseWrapperQuestion(reason); } else if (!isSupportedGradleVersion(gradleVersion)) { String reason = String.format("Gradle version %1$s is not supported.", gradleHomePath.getPath()); msg = createUseWrapperQuestion(reason); } } } if (msg != null) { int answer = showOkCancelDialog(project, msg, GRADLE_SYNC_MSG_TITLE, getQuestionIcon()); createWrapper = answer == Messages.OK; chooseLocalGradleHome = !createWrapper; } } if (createWrapper) { File projectDirPath = getBaseDirPath(project); // attempt to delete the whole gradle wrapper folder. File gradleDirPath = new File(projectDirPath, SdkConstants.FD_GRADLE); if (!delete(gradleDirPath)) { // deletion failed. Let sync continue. return true; } try { createGradleWrapper(projectDirPath); if (distributionType == null) { gradleSettings.setDistributionType(DEFAULT_WRAPPED); } return true; } catch (IOException e) { LOG.info("Failed to create Gradle wrapper for project '" + project.getName() + "'", e); } } else if (distributionType == null || chooseLocalGradleHome) { ChooseGradleHomeDialog dialog = new ChooseGradleHomeDialog(); if (dialog.showAndGet()) { String enteredGradleHomePath = dialog.getEnteredGradleHomePath(); gradleSettings.setGradleHome(enteredGradleHomePath); gradleSettings.setDistributionType(LOCAL); return true; } } return false; } @NotNull private static String createUseWrapperQuestion(@NotNull String reason) { return reason + "\n\n" + "Would you like the project to use the Gradle wrapper?\n" + "(The wrapper will automatically download the latest supported Gradle version).\n\n" + "Click 'OK' to use the Gradle wrapper, or 'Cancel' to manually set the path of a local Gradle distribution."; } static class PreSyncCheckResult { private final boolean mySuccess; @Nullable private final String myFailureCause; @NotNull private static PreSyncCheckResult success() { return new PreSyncCheckResult(true, null); } @NotNull private static PreSyncCheckResult failure(@NotNull String cause) { return new PreSyncCheckResult(false, cause); } private PreSyncCheckResult(boolean success, @Nullable String failureCause) { mySuccess = success; myFailureCause = failureCause; } public boolean isSuccess() { return mySuccess; } @Nullable public String getFailureCause() { return myFailureCause; } } /** * Implementation of "Do not show this dialog in the future" option. This option is displayed as a checkbox in a {@code Messages} dialog. * The state of such checkbox is stored in the IDE's {@code PropertiesComponent} under the name passed in the constructor. */ private static class PropertyDoNotAskOption implements DoNotAskOption { /** The name of the property storing the value of the "Do not show this dialog in the future" option. */ @NotNull private final String myProperty; PropertyDoNotAskOption(@NotNull String property) { myProperty = property; } @Override public boolean isToBeShown() { // Read the stored value. If none is found, return "true" to display the checkbox the first time. return PropertiesComponent.getInstance().getBoolean(myProperty, true); } @Override public void setToBeShown(boolean toBeShown, int exitCode) { // Stores the state of the checkbox into the property. PropertiesComponent.getInstance().setValue(myProperty, String.valueOf(toBeShown)); } @Override public boolean canBeHidden() { // By returning "true", the Messages dialog can hide the checkbox if the user previously set the checkbox as "selected". return true; } @Override public boolean shouldSaveOptionsOnCancel() { // We always want to save the value of the checkbox, regardless of the button pressed in the Messages dialog. return true; } @NotNull @Override public String getDoNotShowMessage() { // This is the text to set in the checkbox. return CommonBundle.message("dialog.options.do.not.show"); } } }