Java tutorial
/* * Copyright (C) 2015 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.actions; import com.android.SdkConstants; import com.android.tools.idea.gradle.dsl.model.GradleBuildModel; import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel; import com.android.tools.idea.gradle.dsl.model.dependencies.DependenciesModel; import com.android.tools.idea.gradle.project.GradleProjectInfo; import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker; import com.android.tools.idea.gradle.project.sync.GradleSyncListener; import com.android.tools.idea.gradle.util.Projects; import com.android.tools.idea.model.AndroidModuleInfo; import com.android.tools.idea.templates.RepositoryUrlManager; import com.android.tools.idea.templates.SupportLibrary; import com.intellij.analysis.AnalysisScope; import com.intellij.analysis.BaseAnalysisActionDialog; import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInspection.inferNullity.InferNullityAnnotationsAction; import com.intellij.codeInspection.inferNullity.NullityInferrer; import com.intellij.history.LocalHistory; import com.intellij.history.LocalHistoryAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Factory; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.refactoring.RefactoringBundle; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewUtil; import com.intellij.usages.*; import com.intellij.util.Processor; import com.intellij.util.SequentialModalProgressTask; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; import static com.android.tools.idea.gradle.dsl.model.dependencies.CommonConfigurationNames.COMPILE; import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; import static com.intellij.openapi.util.text.StringUtil.pluralize; /** * AndroidInferNullityAnnotationAction gives the user the option of adding the correct * component library to the gradle build file. * This file has excerpts of Intellij code. */ public class AndroidInferNullityAnnotationAction extends InferNullityAnnotationsAction { private static final Logger LOG = Logger.getInstance(AndroidInferNullityAnnotationAction.class); private static final String INFER_NULLITY_ANNOTATIONS = "Infer Nullity Annotations"; private static final String ADD_DEPENDENCY = "Add Support Dependency"; private static final int MIN_SDK_WITH_NULLABLE = 19; @Override protected void analyze(@NotNull Project project, @NotNull AnalysisScope scope) { if (!Projects.isBuildWithGradle(project)) { super.analyze(project, scope); return; } int[] fileCount = new int[] { 0 }; PsiDocumentManager.getInstance(project).commitAllDocuments(); UsageInfo[] usageInfos = findUsages(project, scope, fileCount[0]); if (usageInfos == null) return; Map<Module, PsiFile> modules = findModulesFromUsage(usageInfos); if (!checkModules(project, scope, modules)) { return; } if (usageInfos.length < 5) { SwingUtilities.invokeLater(applyRunnable(project, () -> usageInfos)); } else { showUsageView(project, usageInfos, scope, this); } } private static Map<Module, PsiFile> findModulesFromUsage(UsageInfo[] infos) { // We need 1 file from each module that requires changes (the file may be overwritten below): Map<Module, PsiFile> modules = new HashMap<>(); for (UsageInfo info : infos) { PsiElement element = info.getElement(); assert element != null; Module module = ModuleUtilCore.findModuleForPsiElement(element); PsiFile file = element.getContainingFile(); modules.put(module, file); } return modules; } // For Android we need to check SDK version and possibly update the gradle project file protected boolean checkModules(@NotNull Project project, @NotNull AnalysisScope scope, @NotNull Map<Module, PsiFile> modules) { Set<Module> modulesWithoutAnnotations = new HashSet<>(); Set<Module> modulesWithLowVersion = new HashSet<>(); for (Module module : modules.keySet()) { AndroidModuleInfo info = AndroidModuleInfo.get(module); if (info != null && info.getBuildSdkVersion() != null && info.getBuildSdkVersion().getFeatureLevel() < MIN_SDK_WITH_NULLABLE) { modulesWithLowVersion.add(module); } GradleBuildModel buildModel = GradleBuildModel.get(module); if (buildModel == null) { LOG.warn("Unable to find Gradle build model for module " + module.getModuleFilePath()); continue; } boolean dependencyFound = false; DependenciesModel dependenciesModel = buildModel.dependencies(); if (dependenciesModel != null) { for (ArtifactDependencyModel dependency : dependenciesModel.artifacts(COMPILE)) { String notation = dependency.compactNotation().value(); if (notation.startsWith(SdkConstants.APPCOMPAT_LIB_ARTIFACT) || notation.startsWith(SdkConstants.SUPPORT_LIB_ARTIFACT) || notation.startsWith(SdkConstants.ANNOTATIONS_LIB_ARTIFACT)) { dependencyFound = true; break; } } } if (!dependencyFound) { modulesWithoutAnnotations.add(module); } } if (!modulesWithLowVersion.isEmpty()) { Messages.showErrorDialog(project, String.format( "Infer Nullity Annotations requires the project sdk level be set to %1$d or greater.", MIN_SDK_WITH_NULLABLE), "Infer Nullity Annotations"); return false; } if (modulesWithoutAnnotations.isEmpty()) { return true; } String moduleNames = StringUtil.join(modulesWithoutAnnotations, Module::getName, ", "); int count = modulesWithoutAnnotations.size(); String message = String.format( "The %1$s %2$s %3$sn't refer to the existing '%4$s' library with Android nullity annotations. \n\n" + "Would you like to add the %5$s now?", pluralize("module", count), moduleNames, count > 1 ? "do" : "does", SupportLibrary.SUPPORT_ANNOTATIONS.getArtifactId(), pluralize("dependency", count)); if (Messages.showOkCancelDialog(project, message, "Infer Nullity Annotations", Messages.getErrorIcon()) == Messages.OK) { LocalHistoryAction action = LocalHistory.getInstance().startAction(ADD_DEPENDENCY); try { new WriteCommandAction(project, ADD_DEPENDENCY) { @Override protected void run(@NotNull Result result) throws Throwable { RepositoryUrlManager manager = RepositoryUrlManager.get(); String annotationsLibraryCoordinate = manager .getLibraryStringCoordinate(SupportLibrary.SUPPORT_ANNOTATIONS, true); for (Module module : modulesWithoutAnnotations) { addDependency(module, annotationsLibraryCoordinate); } GradleSyncInvoker.Request request = new GradleSyncInvoker.Request() .setGenerateSourcesOnSuccess(false); GradleSyncInvoker.getInstance().requestProjectSync(project, request, new GradleSyncListener.Adapter() { @Override public void syncSucceeded(@NotNull Project project) { restartAnalysis(project, scope); } }); } }.execute(); } finally { action.finish(); } } return false; } // Intellij code from InferNullityAnnotationsAction. private static Runnable applyRunnable(Project project, Computable<UsageInfo[]> computable) { return () -> { LocalHistoryAction action = LocalHistory.getInstance().startAction(INFER_NULLITY_ANNOTATIONS); try { new WriteCommandAction(project, INFER_NULLITY_ANNOTATIONS) { @Override protected void run(@NotNull Result result) throws Throwable { UsageInfo[] infos = computable.compute(); if (infos.length > 0) { Set<PsiElement> elements = new LinkedHashSet<>(); for (UsageInfo info : infos) { PsiElement element = info.getElement(); if (element != null) { ContainerUtil.addIfNotNull(elements, element.getContainingFile()); } } if (!FileModificationService.getInstance().preparePsiElementsForWrite(elements)) return; SequentialModalProgressTask progressTask = new SequentialModalProgressTask(project, INFER_NULLITY_ANNOTATIONS, false); progressTask.setMinIterationTime(200); progressTask.setTask(new AnnotateTask(project, progressTask, infos)); ProgressManager.getInstance().run(progressTask); } else { NullityInferrer.nothingFoundMessage(project); } } }.execute(); } finally { action.finish(); } }; } // Intellij code from InferNullityAnnotationsAction. @Override protected void restartAnalysis(Project project, AnalysisScope scope) { ApplicationManager.getApplication().invokeLater(() -> analyze(project, scope)); } // Intellij code from InferNullityAnnotationsAction. private static void showUsageView(@NotNull Project project, UsageInfo[] usageInfos, @NotNull AnalysisScope scope, AndroidInferNullityAnnotationAction action) { UsageTarget[] targets = UsageTarget.EMPTY_ARRAY; Ref<Usage[]> convertUsagesRef = new Ref<>(); if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( () -> ApplicationManager.getApplication() .runReadAction(() -> convertUsagesRef.set(UsageInfo2UsageAdapter.convert(usageInfos))), "Preprocess Usages", true, project)) { return; } if (convertUsagesRef.isNull()) return; Usage[] usages = convertUsagesRef.get(); UsageViewPresentation presentation = new UsageViewPresentation(); presentation.setTabText("Infer Nullity Preview"); presentation.setShowReadOnlyStatusAsRed(true); presentation.setShowCancelButton(true); presentation.setUsagesString(RefactoringBundle.message("usageView.usagesText")); UsageView usageView = UsageViewManager.getInstance(project).showUsages(targets, usages, presentation, rerunFactory(project, scope, action)); Runnable refactoringRunnable = applyRunnable(project, () -> { Set<UsageInfo> infos = UsageViewUtil.getNotExcludedUsageInfos(usageView); return infos.toArray(new UsageInfo[infos.size()]); }); String canNotMakeString = "Cannot perform operation.\nThere were changes in code after usages have been found.\nPlease perform operation search again."; usageView.addPerformOperationAction(refactoringRunnable, INFER_NULLITY_ANNOTATIONS, canNotMakeString, INFER_NULLITY_ANNOTATIONS, false); } // Intellij code from InferNullityAnnotationsAction. @NotNull private static Factory<UsageSearcher> rerunFactory(@NotNull Project project, @NotNull AnalysisScope scope, AndroidInferNullityAnnotationAction action) { return () -> new UsageInfoSearcherAdapter() { @Override protected UsageInfo[] findUsages() { return action.findUsages(project, scope, scope.getFileCount()); } @Override public void generate(@NotNull Processor<Usage> processor) { processUsages(processor, project); } }; } private static void addDependency(@NotNull Module module, @Nullable String libraryCoordinate) { if (isNotEmpty(libraryCoordinate)) { ModuleRootModificationUtil.updateModel(module, model -> { GradleBuildModel buildModel = GradleBuildModel.get(module); if (buildModel != null) { buildModel.dependencies().addArtifact(COMPILE, libraryCoordinate); buildModel.applyChanges(); } }); } } /* Android nullable annotations do not support annotations on local variables. */ @Override protected JComponent getAdditionalActionSettings(Project project, BaseAnalysisActionDialog dialog) { JComponent panel = super.getAdditionalActionSettings(project, dialog); if (panel != null && GradleProjectInfo.getInstance(project).isBuildWithGradle()) { panel.setVisible(false); } return panel; } }