mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-18 07:15:51 +07:00
195 lines
9.6 KiB
Java
195 lines
9.6 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.codeInsight;
|
|
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.application.PathManager;
|
|
import com.intellij.openapi.application.ex.PathManagerEx;
|
|
import com.intellij.openapi.projectRoots.Sdk;
|
|
import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
|
|
import com.intellij.openapi.roots.OrderRootType;
|
|
import com.intellij.openapi.roots.ProjectRootManager;
|
|
import com.intellij.openapi.util.io.FileUtil;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.vfs.*;
|
|
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.psi.util.PsiFormatUtil;
|
|
import com.intellij.testFramework.IdeaTestCase;
|
|
import com.intellij.testFramework.PsiTestUtil;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.containers.MostlySingularMultiMap;
|
|
import com.intellij.xml.util.XmlUtil;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.jetbrains.idea.eclipse.util.PathUtil;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
|
|
public class ExternalAnnotationsManagerTest extends IdeaTestCase {
|
|
@Override
|
|
protected Sdk getTestProjectJdk() {
|
|
Sdk jdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
|
|
Sdk sdk = PsiTestUtil.addJdkAnnotations(jdk);
|
|
String home = jdk.getHomeDirectory().getParent().getPath();
|
|
VfsRootAccess.allowRootAccess(getTestRootDisposable(), home);
|
|
String toolsPath = home + "/lib/tools.jar!/";
|
|
VirtualFile toolsJar = JarFileSystem.getInstance().findFileByPath(toolsPath);
|
|
|
|
Sdk plusTools = PsiTestUtil.addRootsToJdk(sdk, OrderRootType.CLASSES, toolsJar);
|
|
|
|
Collection<String> utilClassPath = PathManager.getUtilClassPath();
|
|
VirtualFile[] files = utilClassPath.stream()
|
|
.map(path -> path.endsWith(".jar") ?
|
|
JarFileSystem.getInstance() .findFileByPath(FileUtil.toSystemIndependentName(path) + "!/") :
|
|
LocalFileSystem.getInstance() .findFileByPath(FileUtil.toSystemIndependentName(path)))
|
|
.toArray(VirtualFile[]::new);
|
|
|
|
return PsiTestUtil.addRootsToJdk(plusTools, OrderRootType.CLASSES, files);
|
|
}
|
|
|
|
public void testBundledAnnotationXmlSyntax() {
|
|
String root = PathManagerEx.getCommunityHomePath() + "/java/jdkAnnotations";
|
|
findAnnotationsXmlAndCheckSyntax(root);
|
|
}
|
|
|
|
private void findAnnotationsXmlAndCheckSyntax(String root) {
|
|
VirtualFile jdkAnnoRoot = LocalFileSystem.getInstance().findFileByPath(root);
|
|
VfsUtilCore.visitChildrenRecursively(jdkAnnoRoot, new VirtualFileVisitor() {
|
|
@Override
|
|
public boolean visitFile(@NotNull VirtualFile file) {
|
|
if (file.getName().equals("annotations.xml")) {
|
|
String assumedPackage = PathUtil.getRelative(root, file.getParent().getPath()).replaceAll("/",".");
|
|
checkSyntax(file, assumedPackage);
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// some android classes are missing in IDEA, e.g. android.support.annotation.NonNull
|
|
public void _testAndroidAnnotationsXml() {
|
|
VirtualFile lib = LocalFileSystem.getInstance().findFileByPath(PathManagerEx.getCommunityHomePath() + "/android/android/lib");
|
|
VirtualFile[] androidJars = Arrays.stream(lib.getChildren())
|
|
.map(file -> file.getName().endsWith(".jar") ?
|
|
JarFileSystem.getInstance().getJarRootForLocalFile(file) :
|
|
file)
|
|
.toArray(VirtualFile[]::new);
|
|
|
|
ApplicationManager.getApplication().runWriteAction(() -> ProjectRootManager.getInstance(getProject())
|
|
.setProjectSdk(PsiTestUtil.addRootsToJdk(getTestProjectJdk(), OrderRootType.CLASSES, androidJars)));
|
|
|
|
String root = PathManagerEx.getCommunityHomePath() + "/android/android/annotations";
|
|
findAnnotationsXmlAndCheckSyntax(root);
|
|
}
|
|
|
|
private void checkSyntax(@NotNull VirtualFile file, @NotNull String assumedPackage) {
|
|
//System.out.println("file = " + file);
|
|
ExternalAnnotationsManagerImpl manager = (ExternalAnnotationsManagerImpl)ExternalAnnotationsManager.getInstance(getProject());
|
|
PsiFile psiFile = getPsiManager().findFile(file);
|
|
MostlySingularMultiMap<String, BaseExternalAnnotationsManager.AnnotationData> map = manager.getDataFromFile(psiFile);
|
|
for (String externalName : map.keySet()) {
|
|
checkExternalName(psiFile, externalName, assumedPackage);
|
|
|
|
// 'annotation name="org.jetbrains.annotations.NotNull"' should have FQN
|
|
for (BaseExternalAnnotationsManager.AnnotationData annotationData : map.get(externalName)) {
|
|
PsiAnnotation annotation = annotationData.getAnnotation(manager);
|
|
String nameText = annotation.getNameReferenceElement().getText();
|
|
assertClassFqn(nameText, psiFile, externalName, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
private PsiClass assertClassFqn(@NotNull String text, @NotNull PsiFile psiFile, @NotNull String externalName, @Nullable("null means can be any") String assumedPackage) {
|
|
if (!PsiNameHelper.getInstance(getProject()).isQualifiedName(text) || !text.contains(".")) {
|
|
fail("'" + text + "' doesn't seem like a FQN", psiFile, externalName);
|
|
}
|
|
|
|
PsiClass aClass = JavaPsiFacade.getInstance(getProject()).findClass(text, GlobalSearchScope.allScope(getProject()));
|
|
if (aClass == null) {
|
|
fail("'" + text + "' doesn't resolve to a class", psiFile, externalName);
|
|
}
|
|
String packageName = ((PsiClassOwner)aClass.getContainingFile()).getPackageName();
|
|
if (assumedPackage != null && !assumedPackage.equals(packageName)) {
|
|
fail("Wrong package for class '"+text+"'. Expected: '"+assumedPackage+"' but was: '"+packageName+"'", psiFile, externalName);
|
|
}
|
|
return aClass;
|
|
}
|
|
|
|
@Contract("_,_,_-> fail")
|
|
private static void fail(String error, PsiFile psiFile, String externalName) {
|
|
int offset = psiFile.getText().indexOf(XmlUtil.escape(externalName));
|
|
int line = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile).getLineNumber(offset);
|
|
fail(error + "\nFile: " + psiFile.getVirtualFile().getPath() + ":" + (line+1) + " (offset: "+offset+")");
|
|
}
|
|
|
|
private void checkExternalName(@NotNull PsiFile psiFile, @NotNull String externalName, @NotNull String assumedPackage) {
|
|
// 'item name="java.lang.ClassLoader java.net.URL getResource(java.lang.String) 0"' should have all FQNs
|
|
String unescaped = StringUtil.unescapeXml(externalName);
|
|
List<String> words = StringUtil.split(unescaped, " ");
|
|
String className = words.get(0);
|
|
PsiClass aClass = assertClassFqn(className, psiFile, externalName, assumedPackage);
|
|
if (words.size() == 1) return;
|
|
|
|
String rest = unescaped.substring(className.length() + " ".length());
|
|
|
|
if (rest.indexOf('(') == -1) {
|
|
// field
|
|
String field = StringUtil.trim(rest);
|
|
PsiField psiField = aClass.findFieldByName(field, false);
|
|
if (psiField == null) {
|
|
fail("Field '"+field+"' not found in class '"+aClass.getQualifiedName()+"'", psiFile, externalName);
|
|
}
|
|
return;
|
|
}
|
|
String methodName = ContainerUtil.getLastItem(StringUtil.getWordsIn(rest.substring(0, rest.indexOf('('))));
|
|
|
|
String methodSignature = rest.substring(0, rest.indexOf(')') + 1);
|
|
String methodExternalName = className + " " + methodSignature;
|
|
|
|
List<PsiMethod> methods = Arrays.stream(aClass.getMethods())
|
|
.filter(method -> methodExternalName.equals(PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE)))
|
|
.collect(Collectors.toList());
|
|
boolean found = !methods.isEmpty();
|
|
if (!found) {
|
|
List<String> candidates = Arrays.stream(aClass.findMethodsByName(methodName, false))
|
|
.map(method -> XmlUtil.escape(PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE)))
|
|
.collect(Collectors.toList());
|
|
String additionalMsg = candidates.isEmpty() ? "" : "\nMaybe you have meant one of these methods instead:\n"+StringUtil.join(candidates, "\n")+"\n";
|
|
fail("This method was not found in class '"+aClass.getQualifiedName()+"':\n"+"'"+methodSignature+"'"+additionalMsg, psiFile, externalName);
|
|
}
|
|
|
|
String parameterNumberText = StringUtil.trim(rest.substring(rest.indexOf(')') + 1));
|
|
if (parameterNumberText.isEmpty()) return;
|
|
|
|
try {
|
|
int paramNumber = Integer.parseInt(parameterNumberText);
|
|
PsiMethod method = methods.get(0);
|
|
if (method.getParameterList().getParametersCount() <= paramNumber) {
|
|
fail("Parameter number '"+paramNumber+"' is too big for a method '"+methodSignature+"'", psiFile, externalName);
|
|
}
|
|
}
|
|
catch (NumberFormatException e) {
|
|
fail("Parameter number is not an integer: '"+parameterNumberText+"'", psiFile, externalName);
|
|
}
|
|
}
|
|
}
|