diff options
author | Fuyao Zhao <fuyaoz@google.com> | 2015-06-22 11:27:05 -0700 |
---|---|---|
committer | Fuyao Zhao <fuyaoz@google.com> | 2015-08-03 12:22:41 -0700 |
commit | d2b0ecbfdc3fe8858ad615ff39b2c41d1d616512 (patch) | |
tree | 7fe09a51dabb9a8dc64605964489a8090cd27a5a /android/src | |
parent | d0aa6f6e42a8ff80e71b676efb6240446e41d697 (diff) | |
download | idea-d2b0ecbfdc3fe8858ad615ff39b2c41d1d616512.tar.gz |
Change gradle's language setting when trigger language level change quick fix
Change-Id: I7d7438ab1324f9fedd38b43700a69cf9df0e08e1
Diffstat (limited to 'android/src')
3 files changed, 265 insertions, 0 deletions
diff --git a/android/src/META-INF/plugin.xml b/android/src/META-INF/plugin.xml index b6e2ab1e71f..2efec6f7fc6 100755 --- a/android/src/META-INF/plugin.xml +++ b/android/src/META-INF/plugin.xml @@ -733,6 +733,7 @@ <generatedSourcesFilter implementation="com.android.tools.idea.editors.AndroidGeneratedSourcesFilter"/> <codeInsight.unresolvedReferenceQuickFixProvider implementation="com.android.tools.idea.quickfix.AndroidUnresolvedReferenceQuickFixProvider" order="last"/> + <highlightVisitor implementation="com.android.tools.idea.highlight.AndroidHighlightVisitor" order="last"/> </extensions> <extensions defaultExtensionNs="com.intellij.properties"> diff --git a/android/src/com/android/tools/idea/highlight/AndroidHighlightVisitor.java b/android/src/com/android/tools/idea/highlight/AndroidHighlightVisitor.java new file mode 100644 index 00000000000..7c96caa26bd --- /dev/null +++ b/android/src/com/android/tools/idea/highlight/AndroidHighlightVisitor.java @@ -0,0 +1,128 @@ +/* + * 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.highlight; + +import com.android.tools.idea.gradle.parser.GradleBuildFile; +import com.android.tools.idea.quickfix.GradleIncreaseLanguageLevelFix; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightVisitor; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder; +import com.intellij.codeInsight.daemon.impl.analysis.IncreaseLanguageLevelFix; +import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.Condition; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.JavaElementVisitor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; + +import static com.intellij.openapi.module.ModuleUtilCore.findModuleForPsiElement; +import static com.intellij.util.ReflectionUtil.getField; + +public class AndroidHighlightVisitor extends JavaElementVisitor implements HighlightVisitor { + private HighlightInfoHolder myHolder = null; + + @Override + public void visit(@NotNull PsiElement element) { + if (myHolder == null) return; + Module contextModule = findModuleForPsiElement(element); + if (contextModule == null) { + return; + } + + final AndroidFacet facet = AndroidFacet.getInstance(contextModule); + if (facet == null) { + return; + } + + GradleBuildFile gradleBuildFile = GradleBuildFile.get(contextModule); + + for (int i = 0; i < myHolder.size(); i++) { + HighlightInfo info = myHolder.get(i); + + final LanguageLevel[] targetLanguageLevel = {null}; + + try { + info.unregisterQuickFix(new Condition<IntentionAction>() { + @Override + public boolean value(IntentionAction intentionAction) { + if (intentionAction.getClass() == IncreaseLanguageLevelFix.class) { + // TODO add accessor for IncreaseLanguageLevelFix.myLevel + targetLanguageLevel[0] = getField(IncreaseLanguageLevelFix.class, intentionAction, LanguageLevel.class, "myLevel"); + if (targetLanguageLevel[0] != null) return true; + } + return false; + } + }); + } + catch (NullPointerException e) { + // ignore, unregisterQuickFix doesn't have null check + } + if (targetLanguageLevel[0] == null) { + continue; + } + if (targetLanguageLevel[0].isAtLeast(LanguageLevel.JDK_1_8)) { + // We don't support Java 8 yet. + continue; + } + + if (gradleBuildFile == null) { + // Currently our API doesn't address the case that gradle.build file does not exist at the module folder, so just skip for now. + continue; + } + + // Notice we don't need special handling for "try with resources" feature. + // Though unlike other syntactic sugar in Java 7, try with resources depends on the actual Java 7 library, which is not officially + // supported after Android SDK 21, so we can't offer a quickfix to increase the language level to 7. + // But if we are not using later SDK, IDEA will first show type mismatch error instead of syntax error because it requires + // the resource defined to be 'AutoClosable' type. + + QuickFixAction.registerQuickFixAction(info, new GradleIncreaseLanguageLevelFix(targetLanguageLevel[0], gradleBuildFile)); + // TODO when we can't increase the language level, maybe we should change the highlight text to reflect that. + } + } + + @Override + public boolean analyze(@NotNull PsiFile file, boolean updateWholeFile, @NotNull HighlightInfoHolder holder, @NotNull Runnable action) { + myHolder = holder; + try { + action.run(); + } + finally { + myHolder = null; + } + return true; + } + + @Override + public boolean suitableForFile(@NotNull PsiFile file) { + return true; + } + + @NotNull + @Override + public AndroidHighlightVisitor clone() { + return new AndroidHighlightVisitor(); + } + + @Override + public int order() { + return 0; + } +} diff --git a/android/src/com/android/tools/idea/quickfix/GradleIncreaseLanguageLevelFix.java b/android/src/com/android/tools/idea/quickfix/GradleIncreaseLanguageLevelFix.java new file mode 100644 index 00000000000..29083b05ef6 --- /dev/null +++ b/android/src/com/android/tools/idea/quickfix/GradleIncreaseLanguageLevelFix.java @@ -0,0 +1,136 @@ +/* + * 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.quickfix; + +import com.android.tools.idea.gradle.parser.BuildFileKey; +import com.android.tools.idea.gradle.parser.GradleBuildFile; +import com.android.tools.idea.gradle.project.GradleProjectImporter; +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInsight.daemon.impl.analysis.IncreaseLanguageLevelFix; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkVersion; +import com.intellij.openapi.projectRoots.JdkVersionUtil; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.android.tools.idea.gradle.util.GradleUtil.getAndroidProject; + +/** + * The quickfix for increasing language level by modifying the build.gradle and sync the project. + * Most of the code is duplicated from {@link IncreaseLanguageLevelFix} except + * the {@link GradleIncreaseLanguageLevelFix#invoke} method. + */ +public class GradleIncreaseLanguageLevelFix implements IntentionAction { + private static final Logger LOG = Logger.getInstance(GradleIncreaseLanguageLevelFix.class); + + private final LanguageLevel myLevel; + private final GradleBuildFile myBuildFile; + + public GradleIncreaseLanguageLevelFix(@NotNull LanguageLevel targetLevel, @NotNull GradleBuildFile buildFile) { + myLevel = targetLevel; + myBuildFile = buildFile; + } + + @Override + @NotNull + public String getText() { + return CodeInsightBundle.message("set.language.level.to.0", myLevel.getPresentableText()); + } + + @Override + @NotNull + public String getFamilyName() { + return CodeInsightBundle.message("set.language.level"); + } + + private static boolean isJdkSupportsLevel(@Nullable final Sdk jdk, @NotNull LanguageLevel level) { + if (jdk == null) return true; + String versionString = jdk.getVersionString(); + JavaSdkVersion version = versionString == null ? null : JdkVersionUtil.getVersion(versionString); + return version != null && version.getMaxLanguageLevel().isAtLeast(level); + } + + @Override + public boolean isAvailable(@NotNull final Project project, @Nullable final Editor editor, @Nullable final PsiFile file) { + if (file == null) return false; + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) return false; + final Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); + return module != null && isLanguageLevelAcceptable(project, module, myLevel); + } + + private static boolean isLanguageLevelAcceptable(@NotNull Project project, @NotNull Module module, @NotNull LanguageLevel level) { + return isJdkSupportsLevel(getRelevantJdk(project, module), level); + } + + @Override + public void invoke(@NotNull final Project project, @Nullable final Editor editor, @Nullable final PsiFile file) + throws IncorrectOperationException { + if (file == null) return; + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) return; + final Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); + final LanguageLevel moduleLevel = module == null ? null : LanguageLevelModuleExtensionImpl.getInstance(module).getLanguageLevel(); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + if (moduleLevel != null && isLanguageLevelAcceptable(project, module, myLevel)) { + String gradleJavaVersion = getGradleJavaVersionString(myLevel); + if (getAndroidProject(module) != null) { + myBuildFile.setValue(BuildFileKey.SOURCE_COMPATIBILITY, gradleJavaVersion); + myBuildFile.setValue(BuildFileKey.TARGET_COMPATIBILITY, gradleJavaVersion); + } else { + LOG.error("Setting language level on Java module is not supported"); + } + GradleProjectImporter.getInstance().requestProjectSync(project, null); + } + else { + LOG.error("Tried to set language level without specify a module"); + } + } + }); + } + + @Nullable + private static Sdk getRelevantJdk(@NotNull Project project, @Nullable Module module) { + Sdk projectJdk = ProjectRootManager.getInstance(project).getProjectSdk(); + Sdk moduleJdk = module == null ? null : ModuleRootManager.getInstance(module).getSdk(); + return moduleJdk == null ? projectJdk : moduleJdk; + } + + @Override + public boolean startInWriteAction() { + return false; + } + + @NotNull + private static String getGradleJavaVersionString(@NotNull LanguageLevel languageLevel) { + return "JavaVersion." + languageLevel.name().replaceAll("JDK", "VERSION"); + } +} |