mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-19 01:09:52 +07:00
346 lines
14 KiB
Java
346 lines
14 KiB
Java
/*
|
|
* Copyright 2000-2016 JetBrains s.r.o.
|
|
*
|
|
* 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.intellij.codeInspection.bytecodeAnalysis;
|
|
|
|
import com.intellij.codeInsight.AnnotationUtil;
|
|
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
|
import com.intellij.codeInsight.daemon.GutterMark;
|
|
import com.intellij.openapi.application.ex.PathManagerEx;
|
|
import com.intellij.openapi.projectRoots.Sdk;
|
|
import com.intellij.openapi.projectRoots.SdkModificator;
|
|
import com.intellij.openapi.roots.AnnotationOrderRootType;
|
|
import com.intellij.openapi.roots.ModifiableRootModel;
|
|
import com.intellij.openapi.roots.ModuleRootModificationUtil;
|
|
import com.intellij.openapi.roots.libraries.Library;
|
|
import com.intellij.openapi.roots.libraries.LibraryTable;
|
|
import com.intellij.openapi.util.registry.Registry;
|
|
import com.intellij.openapi.util.registry.RegistryValue;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
|
import com.intellij.openapi.vfs.VfsUtilCore;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.openapi.vfs.VirtualFileVisitor;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.psi.util.PsiFormatUtil;
|
|
import com.intellij.testFramework.PsiTestUtil;
|
|
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
|
import com.intellij.util.ArrayUtil;
|
|
import com.intellij.util.AsynchConsumer;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.jetbrains.java.decompiler.IdeaDecompiler;
|
|
|
|
import java.security.MessageDigest;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* @author lambdamix
|
|
*/
|
|
public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestCase {
|
|
private static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
|
|
|
|
private MessageDigest myMessageDigest;
|
|
private final List<String> myDiffs = new ArrayList<>();
|
|
private boolean myNullableMethodRegistryValue;
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
myMessageDigest = BytecodeAnalysisConverter.getMessageDigest();
|
|
RegistryValue registryValue = Registry.get(ProjectBytecodeAnalysis.NULLABLE_METHOD);
|
|
myNullableMethodRegistryValue = registryValue.asBoolean();
|
|
registryValue.setValue(true);
|
|
}
|
|
|
|
@Override
|
|
protected void tearDown() throws Exception {
|
|
try {
|
|
Registry.get(ProjectBytecodeAnalysis.NULLABLE_METHOD).setValue(myNullableMethodRegistryValue);
|
|
}
|
|
finally {
|
|
super.tearDown();
|
|
}
|
|
}
|
|
|
|
private void setUpLibraries() {
|
|
String libDir = PathManagerEx.getCommunityHomePath() + "/lib";
|
|
PsiTestUtil.addLibrary(myModule, "velocity", libDir, new String[]{"/velocity.jar!/"}, ArrayUtil.EMPTY_STRING_ARRAY);
|
|
}
|
|
|
|
private void setUpExternalUpAnnotations() {
|
|
String annotationsPath = PathManagerEx.getTestDataPath() + "/codeInspection/bytecodeAnalysis/annotations";
|
|
VirtualFile annotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath);
|
|
assertNotNull(annotationsDir);
|
|
|
|
ModuleRootModificationUtil.updateModel(myModule, new AsynchConsumer<ModifiableRootModel>() {
|
|
@Override
|
|
public void finished() { }
|
|
|
|
@Override
|
|
public void consume(ModifiableRootModel modifiableRootModel) {
|
|
LibraryTable libraryTable = modifiableRootModel.getModuleLibraryTable();
|
|
Library[] libs = libraryTable.getLibraries();
|
|
for (Library library : libs) {
|
|
Library.ModifiableModel libraryModel = library.getModifiableModel();
|
|
libraryModel.addRoot(annotationsDir, AnnotationOrderRootType.getInstance());
|
|
libraryModel.commit();
|
|
}
|
|
Sdk sdk = modifiableRootModel.getSdk();
|
|
if (sdk != null) {
|
|
Sdk clone;
|
|
try {
|
|
clone = (Sdk)sdk.clone();
|
|
}
|
|
catch (CloneNotSupportedException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
SdkModificator sdkModificator = clone.getSdkModificator();
|
|
sdkModificator.addRoot(annotationsDir, AnnotationOrderRootType.getInstance());
|
|
sdkModificator.commitChanges();
|
|
modifiableRootModel.setSdk(clone);
|
|
}
|
|
}
|
|
});
|
|
|
|
VfsUtilCore.visitChildrenRecursively(annotationsDir, new VirtualFileVisitor() { });
|
|
annotationsDir.refresh(false, true);
|
|
}
|
|
|
|
private void openDecompiledClass(String name) {
|
|
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(name, GlobalSearchScope.allScope(getProject()));
|
|
assertNotNull(psiClass);
|
|
myFixture.openFileInEditor(psiClass.getContainingFile().getVirtualFile());
|
|
|
|
String documentText = myFixture.getEditor().getDocument().getText();
|
|
assertTrue(documentText, documentText.startsWith(IdeaDecompiler.BANNER));
|
|
}
|
|
|
|
public void testInferredAnnoGutter() {
|
|
setUpLibraries();
|
|
openDecompiledClass("org.apache.velocity.util.ExceptionUtils");
|
|
checkHasGutter("<html><i>Inferred</i> annotations available. Full signature:<p>\n" +
|
|
"<i>@Contract("null,_,_->null")</i> \n" +
|
|
"public static Throwable <b>createWithCause</b>(");
|
|
}
|
|
|
|
public void testExternalAnnoGutter() {
|
|
setUpExternalUpAnnotations();
|
|
openDecompiledClass("java.lang.Boolean");
|
|
checkHasGutter("<html>External and <i>inferred</i> annotations available. Full signature:<p>\n" +
|
|
"@org.jetbrains.annotations.Contract("null->false") \n" +
|
|
"private static boolean <b>toBoolean</b>(@org.jetbrains.annotations.Nullable String var0)</html>");
|
|
}
|
|
|
|
private void checkHasGutter(String expectedText) {
|
|
Collection<String> gutters = ContainerUtil.mapNotNull(myFixture.findAllGutters(), GutterMark::getTooltipText);
|
|
String contractMark = ContainerUtil.find(gutters, mark -> mark.contains(expectedText));
|
|
assertNotNull(StringUtil.join(gutters, "\n"), contractMark);
|
|
}
|
|
|
|
public void testSdkAndLibAnnotations() {
|
|
setUpLibraries();
|
|
setUpExternalUpAnnotations();
|
|
|
|
PsiPackage rootPackage = JavaPsiFacade.getInstance(getProject()).findPackage("");
|
|
assert rootPackage != null;
|
|
|
|
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
|
|
JavaRecursiveElementVisitor visitor = new JavaRecursiveElementVisitor() {
|
|
@Override
|
|
public void visitPackage(PsiPackage aPackage) {
|
|
// annotations are in class paths, but we are not interested in inferred annotations for them
|
|
if ("org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) {
|
|
return;
|
|
}
|
|
for (PsiPackage subPackage : aPackage.getSubPackages(scope)) {
|
|
visitPackage(subPackage);
|
|
}
|
|
for (PsiClass aClass : aPackage.getClasses(scope)) {
|
|
for (PsiMethod method : aClass.getMethods()) {
|
|
checkMethodAnnotations(method);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
rootPackage.accept(visitor);
|
|
assertEmpty(myDiffs);
|
|
}
|
|
|
|
public void _testExportInferredAnnotations() {
|
|
exportInferredAnnotations();
|
|
}
|
|
|
|
private void exportInferredAnnotations() {
|
|
setUpLibraries();
|
|
setUpExternalUpAnnotations();
|
|
|
|
PsiPackage rootPackage = JavaPsiFacade.getInstance(getProject()).findPackage("");
|
|
assert rootPackage != null;
|
|
|
|
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
|
|
JavaRecursiveElementVisitor visitor = new JavaRecursiveElementVisitor() {
|
|
@Override
|
|
public void visitPackage(PsiPackage aPackage) {
|
|
// annotations are in class paths, but we are not interested in inferred annotations for them
|
|
if ("org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) {
|
|
return;
|
|
}
|
|
for (PsiPackage subPackage : aPackage.getSubPackages(scope)) {
|
|
visitPackage(subPackage);
|
|
}
|
|
for (PsiClass aClass : aPackage.getClasses(scope)) {
|
|
processClass(aClass);
|
|
for (PsiClass innerClass : aClass.getInnerClasses()) {
|
|
processClass(innerClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void processClass(PsiClass aClass) {
|
|
for (PsiMethod method : aClass.getMethods()) {
|
|
exportMethodAnnotations(method);
|
|
}
|
|
for (PsiClass innerClass : aClass.getInnerClasses()) {
|
|
processClass(innerClass);
|
|
}
|
|
}
|
|
};
|
|
|
|
rootPackage.accept(visitor);
|
|
}
|
|
|
|
private void exportMethodAnnotations(PsiMethod method) {
|
|
// @Contract
|
|
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
|
if (inferredContractAnnotation != null) {
|
|
PsiNameValuePair[] attributes = inferredContractAnnotation.getParameterList().getAttributes();
|
|
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT, method.getContainingFile(), attributes);
|
|
}
|
|
|
|
{
|
|
// @NotNull method
|
|
PsiAnnotation inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL);
|
|
if (inferredNotNullMethodAnnotation != null) {
|
|
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, AnnotationUtil.NOT_NULL, method.getContainingFile(), null);
|
|
}
|
|
}
|
|
|
|
{
|
|
// @Nullable method
|
|
PsiAnnotation inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE);
|
|
if (inferredNullableMethodAnnotation != null) {
|
|
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, AnnotationUtil.NULLABLE, method.getContainingFile(), null);
|
|
}
|
|
}
|
|
|
|
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
|
{
|
|
// @NotNull parameter
|
|
PsiAnnotation inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL);
|
|
if (inferredNotNull != null) {
|
|
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(parameter, AnnotationUtil.NOT_NULL, method.getContainingFile(), null);
|
|
}
|
|
}
|
|
|
|
{
|
|
// @Nullable parameter
|
|
PsiAnnotation inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE);
|
|
if (inferredNullable != null) {
|
|
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(parameter, AnnotationUtil.NULLABLE, method.getContainingFile(), null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkMethodAnnotations(PsiMethod method) {
|
|
if (ProjectBytecodeAnalysis.getKey(method, myMessageDigest) == null) {
|
|
return;
|
|
}
|
|
|
|
String methodKey = PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE);
|
|
|
|
{
|
|
// @NotNull method
|
|
String externalNotNullMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
|
String inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
|
|
|
if (!externalNotNullMethodAnnotation.equals(inferredNotNullMethodAnnotation)) {
|
|
myDiffs.add(methodKey + ": " + externalNotNullMethodAnnotation + " != " + inferredNotNullMethodAnnotation);
|
|
}
|
|
}
|
|
|
|
{
|
|
// @Nullable method
|
|
String externalNullableMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
|
String inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
|
|
|
if (!externalNullableMethodAnnotation.equals(inferredNullableMethodAnnotation)) {
|
|
myDiffs.add(methodKey + ": " + externalNullableMethodAnnotation + " != " + inferredNullableMethodAnnotation);
|
|
}
|
|
}
|
|
|
|
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
|
String parameterKey = PsiFormatUtil.getExternalName(parameter, false, Integer.MAX_VALUE);
|
|
|
|
{
|
|
// @NotNull parameter
|
|
String externalNotNull = findExternalAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
|
String inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
|
if (!externalNotNull.equals(inferredNotNull)) {
|
|
myDiffs.add(parameterKey + ": " + externalNotNull + " != " + inferredNotNull);
|
|
}
|
|
}
|
|
|
|
{
|
|
// @Nullable parameter
|
|
String externalNullable = findExternalAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
|
String inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
|
if (!externalNullable.equals(inferredNullable)) {
|
|
myDiffs.add(parameterKey + ": " + externalNullable + " != " + inferredNullable);
|
|
}
|
|
}
|
|
}
|
|
|
|
// @Contract
|
|
PsiAnnotation externalContractAnnotation = findExternalAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
|
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
|
|
|
String externalContractAnnotationText =
|
|
externalContractAnnotation == null ? "null" : externalContractAnnotation.getText();
|
|
String inferredContractAnnotationText =
|
|
inferredContractAnnotation == null ? "null" : inferredContractAnnotation.getText();
|
|
|
|
if (!externalContractAnnotationText.equals(inferredContractAnnotationText)) {
|
|
myDiffs.add(methodKey + ": " + externalContractAnnotationText + " != " + inferredContractAnnotationText);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
private PsiAnnotation findInferredAnnotation(PsiModifierListOwner owner, String fqn) {
|
|
return ProjectBytecodeAnalysis.getInstance(getProject()).findInferredAnnotation(owner, fqn);
|
|
}
|
|
|
|
@Nullable
|
|
private PsiAnnotation findExternalAnnotation(PsiModifierListOwner owner, String fqn) {
|
|
return ExternalAnnotationsManager.getInstance(myModule.getProject()).findExternalAnnotation(owner, fqn);
|
|
}
|
|
}
|