Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize class namespace loading cache Doctrine models, to reduce wall time calling #2363

Merged
merged 1 commit into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.*;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.Function;
Expand Down Expand Up @@ -45,6 +46,8 @@ public class EntityHelper {

public static final ExtensionPointName<DoctrineModelProvider> MODEL_POINT_NAME = new ExtensionPointName<>("fr.adrienbrault.idea.symfony2plugin.extension.DoctrineModelProvider");

private static final Key<CachedValue<Collection<DoctrineModelCached>>> SYMFONY_DOCTRINE_MODEL_CACHE = new Key<>("SYMFONY_DOCTRINE_MODEL_CACHE");

final public static String[] ANNOTATION_FIELDS = new String[] {
"\\Doctrine\\ORM\\Mapping\\Column",
"\\Doctrine\\ORM\\Mapping\\OneToOne",
Expand Down Expand Up @@ -693,11 +696,10 @@ public static void attachAnnotationInformation(@NotNull PhpClass phpClass, @NotN

}

/**
* One PhpClass can have multiple targets and names @TODO: refactor
*/
public static Collection<DoctrineModel> getModelClasses(final Project project) {
private record DoctrineModelCached(@NotNull String phpClass, @Nullable String doctrineShortcut, @Nullable String doctrineNamespace) {
}

private static Collection<DoctrineModelCached> getModelClassesInner(final Project project) {
HashMap<String, String> shortcutNames = new HashMap<>() {{
putAll(ServiceXmlParserFactory.getInstance(project, EntityNamesServiceParser.class).getEntityNameMap());
putAll(ServiceXmlParserFactory.getInstance(project, DocumentNamespacesParser.class).getNamespaceMap());
Expand All @@ -715,7 +717,7 @@ public static Collection<DoctrineModel> getModelClasses(final Project project) {
// class fqn fallback
Collection<DoctrineModel> doctrineModels = getModelClasses(project, shortcutNames);
for (PhpClass phpClass : DoctrineMetadataUtil.getModels(project)) {
if(containsDoctrineModelClass(doctrineModels, phpClass)) {
if (containsDoctrineModelClass(doctrineModels, phpClass)) {
continue;
}

Expand All @@ -729,6 +731,40 @@ public static Collection<DoctrineModel> getModelClasses(final Project project) {
}
}

return doctrineModels.stream().map(
doctrineModel -> new DoctrineModelCached(doctrineModel.getPhpClass().getFQN(), doctrineModel.getDoctrineShortcut(), doctrineModel.getDoctrineNamespace())
).toList();
}

/**
* One PhpClass can have multiple targets and names @TODO: refactor
*/
public static Collection<DoctrineModel> getModelClasses(@NotNull final Project project) {
Collection<DoctrineModelCached> modelClasses = CachedValuesManager.getManager(project).getCachedValue(
project,
SYMFONY_DOCTRINE_MODEL_CACHE,
() -> CachedValueProvider.Result.create(getModelClassesInner(project), PsiModificationTracker.MODIFICATION_COUNT),
false
);

PhpIndex phpIndex = PhpIndex.getInstance(project);
Collection<DoctrineModel> doctrineModels = new ArrayList<>();
for (DoctrineModelCached doctrineModelCached : modelClasses) {
Collection<PhpClass> classesByFQN = phpIndex.getClassesByFQN(doctrineModelCached.phpClass);
if (classesByFQN.isEmpty()) {
continue;
}

doctrineModels.add(new DoctrineModel(classesByFQN.iterator().next(), doctrineModelCached.doctrineShortcut, doctrineModelCached.doctrineNamespace));
}

DoctrineModelProviderParameter containerLoaderExtensionParameter = new DoctrineModelProviderParameter(project, new ArrayList<>());
for (DoctrineModelProvider provider : EntityHelper.MODEL_POINT_NAME.getExtensions()) {
for (DoctrineModelProviderParameter.DoctrineModel doctrineModel: provider.collectModels(containerLoaderExtensionParameter)) {
doctrineModels.add(new DoctrineModel(doctrineModel.getPhpClass(), doctrineModel.getName()));
}
}

return doctrineModels;
}

Expand All @@ -742,18 +778,17 @@ private static boolean containsDoctrineModelClass(@NotNull Collection<DoctrineMo
return false;
}

public static Collection<DoctrineModel> getModelClasses(Project project, Map<String, String> shortcutNames) {

public static Collection<DoctrineModel> getModelClasses(@NotNull Project project, @NotNull Map<String, String> shortcutNames) {
PhpClass repositoryInterface = PhpElementsUtil.getInterface(PhpIndex.getInstance(project), DoctrineTypes.REPOSITORY_INTERFACE);

if(repositoryInterface == null) {
if (repositoryInterface == null) {
repositoryInterface = PhpElementsUtil.getInterface(PhpIndex.getInstance(project), "\\Doctrine\\Persistence\\ObjectRepository");
}

Collection<DoctrineModel> models = new ArrayList<>();
for (Map.Entry<String, String> entry : shortcutNames.entrySet()) {
for(PhpClass phpClass: PhpIndexUtil.getPhpClassInsideNamespace(project, entry.getValue())) {
if(repositoryInterface != null && !isEntity(phpClass, repositoryInterface)) {
for (PhpClass phpClass: PhpIndexUtil.getPhpClassInsideNamespace(project, entry.getValue(), true)) {
if (repositoryInterface != null && !isEntity(phpClass, repositoryInterface)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.openapi.project.Project;
import com.intellij.util.Processor;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.php.util.PhpContractUtil;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.*;

/**
* @author Daniel Espendiller <[email protected]>
Expand All @@ -29,7 +23,19 @@ public class PhpIndexUtil {
*/
@NotNull
public static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull Project project, @NotNull String namespaceName) {
return getPhpClassInsideNamespace(PhpIndex.getInstance(project), namespaceName, 10);
return getPhpClassInsideNamespace(PhpIndex.getInstance(project), namespaceName, false);
}

/**
* Collect PhpClass which are inside current namespace and in sub-namespaces
*
* @param project current project
* @param namespaceName namespace name should start with \ and end with "\"
* @return classes inside namespace and sub-namespace
*/
@NotNull
public static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull Project project, @NotNull String namespaceName, boolean excludeInterfaces) {
return getPhpClassInsideNamespace(PhpIndex.getInstance(project), namespaceName, excludeInterfaces);
}

public static Collection<PhpClass> getAllSubclasses(@NotNull Project project, @NotNull String clazz) {
Expand All @@ -45,20 +51,16 @@ public static Collection<PhpClass> getAllSubclasses(@NotNull Project project, @N


@NotNull
private static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull PhpIndex phpIndex, @NotNull String namespaceName, int maxDeep) {
private static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull PhpIndex phpIndex, @NotNull String namespaceName, boolean excludeInterfaces) {
PhpContractUtil.assertFqn(namespaceName);

Collection<String> classes = new HashSet<>() {{
addAll(phpIndex.getAllClassFqns(PrefixMatcher.ALWAYS_TRUE));
addAll(phpIndex.getAllInterfacesFqns(PrefixMatcher.ALWAYS_TRUE));
}};

Set<String> stringStream = classes.stream()
.filter(s -> s.toLowerCase().startsWith(StringUtils.stripEnd(namespaceName.toLowerCase(), "\\") + "\\"))
.collect(Collectors.toSet());
Collection<String> classes = new HashSet<>(phpIndex.getAllClassFqns(new MyPrefixMatcher(namespaceName)));
if (!excludeInterfaces) {
classes.addAll(phpIndex.getAllInterfacesFqns(new MyPrefixMatcher(namespaceName)));
}

Collection<PhpClass> clazzes = new HashSet<>();
for (String s : stringStream) {
for (String s : classes) {
clazzes.addAll(phpIndex.getAnyByFQN(s));
}

Expand All @@ -73,4 +75,23 @@ public static boolean hasNamespace(@NotNull Project project, @NotNull String nam

return !PhpIndex.getInstance(project).getChildNamespacesByParentName(namespaceName + "\\").isEmpty();
}

private static class MyPrefixMatcher extends PrefixMatcher {
private final String namespaceName;

public MyPrefixMatcher(@NotNull String namespaceName) {
super(namespaceName);
this.namespaceName = namespaceName;
}

@Override
public boolean prefixMatches(@NotNull String name) {
return name.startsWith(namespaceName);
}

@Override
public @NotNull PrefixMatcher cloneWithPrefix(@NotNull String prefix) {
return new MyPrefixMatcher(prefix);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public DoctrineModel(@NotNull PhpClass phpClass, @Nullable String doctrineShortc
this.doctrineNamespace = doctrineNamespace;
}

@Nullable
public String getDoctrineShortcut() {
return doctrineShortcut;
}

@NotNull
public PhpClass getPhpClass() {
return phpClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
import fr.adrienbrault.idea.symfony2plugin.util.PhpIndexUtil;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -26,22 +25,32 @@ public void testGetPhpClassInsideNamespace()
{
List<String> foobar = PhpIndexUtil.getPhpClassInsideNamespace(getProject(), "\\Foobar").stream()
.map(PhpNamedElement::getFQN)
.sorted()
.collect(Collectors.toList());

assertEquals(
"\\Foobar\\Class1,\\Foobar\\Class2,\\Foobar\\Foobar2\\Foobar3\\Class1,\\Foobar\\Foobar2\\Foobar3\\Class2,\\Foobar\\Foobar2\\Foobar3\\Interface1,\\Foobar\\Foobar2\\Foobar3\\Interface2,\\Foobar\\Foobar2\\FoobarNot\\Class1,\\Foobar\\Foobar2\\FoobarNot\\Class2,\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1,\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2,\\Foobar\\Interface1,\\Foobar\\Interface2",
StringUtils.join(foobar, ",")
assertContainsElements(
foobar,
"\\Foobar\\Class1",
"\\Foobar\\Class2",
"\\Foobar\\Foobar2\\Foobar3\\Class1",
"\\Foobar\\Foobar2\\Foobar3\\Class2",
"\\Foobar\\Foobar2\\Foobar3\\Interface1",
"\\Foobar\\Foobar2\\Foobar3\\Interface2",
"\\Foobar\\Foobar2\\FoobarNot\\Class1",
"\\Foobar\\Foobar2\\FoobarNot\\Class2",
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1",
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2",
"\\Foobar\\Interface1",
"\\Foobar\\Interface2"
);

List<String> foobar2 = PhpIndexUtil.getPhpClassInsideNamespace(getProject(), "\\Foobar\\Foobar2\\Foobar\\Foobar4\\").stream()
.map(PhpNamedElement::getFQN)
.sorted()
.collect(Collectors.toList());

assertEquals(
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1,\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2",
StringUtils.join(foobar2, ",")
assertContainsElements(
foobar2,
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1",
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2"
);
}

Expand Down
Loading