Skip to content

Commit d5d5e1a

Browse files
authored
Merge pull request #2363 from Haehnchen/feature/model-wall-time
optimize class namespace loading cache Doctrine models, to reduce wall time calling
2 parents d27a256 + ddb89f9 commit d5d5e1a

File tree

4 files changed

+108
-38
lines changed

4 files changed

+108
-38
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import com.intellij.openapi.extensions.ExtensionPointName;
44
import com.intellij.openapi.project.Project;
5+
import com.intellij.openapi.util.Key;
56
import com.intellij.openapi.util.Pair;
67
import com.intellij.openapi.vfs.VirtualFile;
78
import com.intellij.psi.PsiElement;
89
import com.intellij.psi.PsiFile;
910
import com.intellij.psi.PsiManager;
10-
import com.intellij.psi.util.PsiTreeUtil;
11+
import com.intellij.psi.util.*;
1112
import com.intellij.psi.xml.XmlFile;
1213
import com.intellij.psi.xml.XmlTag;
1314
import com.intellij.util.Function;
@@ -45,6 +46,8 @@ public class EntityHelper {
4546

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

49+
private static final Key<CachedValue<Collection<DoctrineModelCached>>> SYMFONY_DOCTRINE_MODEL_CACHE = new Key<>("SYMFONY_DOCTRINE_MODEL_CACHE");
50+
4851
final public static String[] ANNOTATION_FIELDS = new String[] {
4952
"\\Doctrine\\ORM\\Mapping\\Column",
5053
"\\Doctrine\\ORM\\Mapping\\OneToOne",
@@ -693,11 +696,10 @@ public static void attachAnnotationInformation(@NotNull PhpClass phpClass, @NotN
693696

694697
}
695698

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

702+
private static Collection<DoctrineModelCached> getModelClassesInner(final Project project) {
701703
HashMap<String, String> shortcutNames = new HashMap<>() {{
702704
putAll(ServiceXmlParserFactory.getInstance(project, EntityNamesServiceParser.class).getEntityNameMap());
703705
putAll(ServiceXmlParserFactory.getInstance(project, DocumentNamespacesParser.class).getNamespaceMap());
@@ -715,7 +717,7 @@ public static Collection<DoctrineModel> getModelClasses(final Project project) {
715717
// class fqn fallback
716718
Collection<DoctrineModel> doctrineModels = getModelClasses(project, shortcutNames);
717719
for (PhpClass phpClass : DoctrineMetadataUtil.getModels(project)) {
718-
if(containsDoctrineModelClass(doctrineModels, phpClass)) {
720+
if (containsDoctrineModelClass(doctrineModels, phpClass)) {
719721
continue;
720722
}
721723

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

734+
return doctrineModels.stream().map(
735+
doctrineModel -> new DoctrineModelCached(doctrineModel.getPhpClass().getFQN(), doctrineModel.getDoctrineShortcut(), doctrineModel.getDoctrineNamespace())
736+
).toList();
737+
}
738+
739+
/**
740+
* One PhpClass can have multiple targets and names @TODO: refactor
741+
*/
742+
public static Collection<DoctrineModel> getModelClasses(@NotNull final Project project) {
743+
Collection<DoctrineModelCached> modelClasses = CachedValuesManager.getManager(project).getCachedValue(
744+
project,
745+
SYMFONY_DOCTRINE_MODEL_CACHE,
746+
() -> CachedValueProvider.Result.create(getModelClassesInner(project), PsiModificationTracker.MODIFICATION_COUNT),
747+
false
748+
);
749+
750+
PhpIndex phpIndex = PhpIndex.getInstance(project);
751+
Collection<DoctrineModel> doctrineModels = new ArrayList<>();
752+
for (DoctrineModelCached doctrineModelCached : modelClasses) {
753+
Collection<PhpClass> classesByFQN = phpIndex.getClassesByFQN(doctrineModelCached.phpClass);
754+
if (classesByFQN.isEmpty()) {
755+
continue;
756+
}
757+
758+
doctrineModels.add(new DoctrineModel(classesByFQN.iterator().next(), doctrineModelCached.doctrineShortcut, doctrineModelCached.doctrineNamespace));
759+
}
760+
761+
DoctrineModelProviderParameter containerLoaderExtensionParameter = new DoctrineModelProviderParameter(project, new ArrayList<>());
762+
for (DoctrineModelProvider provider : EntityHelper.MODEL_POINT_NAME.getExtensions()) {
763+
for (DoctrineModelProviderParameter.DoctrineModel doctrineModel: provider.collectModels(containerLoaderExtensionParameter)) {
764+
doctrineModels.add(new DoctrineModel(doctrineModel.getPhpClass(), doctrineModel.getName()));
765+
}
766+
}
767+
732768
return doctrineModels;
733769
}
734770

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

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

749-
if(repositoryInterface == null) {
784+
if (repositoryInterface == null) {
750785
repositoryInterface = PhpElementsUtil.getInterface(PhpIndex.getInstance(project), "\\Doctrine\\Persistence\\ObjectRepository");
751786
}
752787

753788
Collection<DoctrineModel> models = new ArrayList<>();
754789
for (Map.Entry<String, String> entry : shortcutNames.entrySet()) {
755-
for(PhpClass phpClass: PhpIndexUtil.getPhpClassInsideNamespace(project, entry.getValue())) {
756-
if(repositoryInterface != null && !isEntity(phpClass, repositoryInterface)) {
790+
for (PhpClass phpClass: PhpIndexUtil.getPhpClassInsideNamespace(project, entry.getValue(), true)) {
791+
if (repositoryInterface != null && !isEntity(phpClass, repositoryInterface)) {
757792
continue;
758793
}
759794

src/main/java/fr/adrienbrault/idea/symfony2plugin/util/PhpIndexUtil.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@
22

33
import com.intellij.codeInsight.completion.PrefixMatcher;
44
import com.intellij.openapi.project.Project;
5-
import com.intellij.util.Processor;
65
import com.jetbrains.php.PhpIndex;
76
import com.jetbrains.php.lang.psi.elements.PhpClass;
87
import com.jetbrains.php.util.PhpContractUtil;
9-
import org.apache.commons.lang3.StringUtils;
108
import org.jetbrains.annotations.NotNull;
119

12-
import java.util.ArrayList;
13-
import java.util.Collection;
14-
import java.util.HashSet;
15-
import java.util.Set;
16-
import java.util.stream.Collectors;
10+
import java.util.*;
1711

1812
/**
1913
* @author Daniel Espendiller <daniel@espendiller.net>
@@ -29,7 +23,19 @@ public class PhpIndexUtil {
2923
*/
3024
@NotNull
3125
public static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull Project project, @NotNull String namespaceName) {
32-
return getPhpClassInsideNamespace(PhpIndex.getInstance(project), namespaceName, 10);
26+
return getPhpClassInsideNamespace(PhpIndex.getInstance(project), namespaceName, false);
27+
}
28+
29+
/**
30+
* Collect PhpClass which are inside current namespace and in sub-namespaces
31+
*
32+
* @param project current project
33+
* @param namespaceName namespace name should start with \ and end with "\"
34+
* @return classes inside namespace and sub-namespace
35+
*/
36+
@NotNull
37+
public static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull Project project, @NotNull String namespaceName, boolean excludeInterfaces) {
38+
return getPhpClassInsideNamespace(PhpIndex.getInstance(project), namespaceName, excludeInterfaces);
3339
}
3440

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

4652

4753
@NotNull
48-
private static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull PhpIndex phpIndex, @NotNull String namespaceName, int maxDeep) {
54+
private static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull PhpIndex phpIndex, @NotNull String namespaceName, boolean excludeInterfaces) {
4955
PhpContractUtil.assertFqn(namespaceName);
5056

51-
Collection<String> classes = new HashSet<>() {{
52-
addAll(phpIndex.getAllClassFqns(PrefixMatcher.ALWAYS_TRUE));
53-
addAll(phpIndex.getAllInterfacesFqns(PrefixMatcher.ALWAYS_TRUE));
54-
}};
55-
56-
Set<String> stringStream = classes.stream()
57-
.filter(s -> s.toLowerCase().startsWith(StringUtils.stripEnd(namespaceName.toLowerCase(), "\\") + "\\"))
58-
.collect(Collectors.toSet());
57+
Collection<String> classes = new HashSet<>(phpIndex.getAllClassFqns(new MyPrefixMatcher(namespaceName)));
58+
if (!excludeInterfaces) {
59+
classes.addAll(phpIndex.getAllInterfacesFqns(new MyPrefixMatcher(namespaceName)));
60+
}
5961

6062
Collection<PhpClass> clazzes = new HashSet<>();
61-
for (String s : stringStream) {
63+
for (String s : classes) {
6264
clazzes.addAll(phpIndex.getAnyByFQN(s));
6365
}
6466

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

7476
return !PhpIndex.getInstance(project).getChildNamespacesByParentName(namespaceName + "\\").isEmpty();
7577
}
78+
79+
private static class MyPrefixMatcher extends PrefixMatcher {
80+
private final String namespaceName;
81+
82+
public MyPrefixMatcher(@NotNull String namespaceName) {
83+
super(namespaceName);
84+
this.namespaceName = namespaceName;
85+
}
86+
87+
@Override
88+
public boolean prefixMatches(@NotNull String name) {
89+
return name.startsWith(namespaceName);
90+
}
91+
92+
@Override
93+
public @NotNull PrefixMatcher cloneWithPrefix(@NotNull String prefix) {
94+
return new MyPrefixMatcher(prefix);
95+
}
96+
}
7697
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/util/dict/DoctrineModel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public DoctrineModel(@NotNull PhpClass phpClass, @Nullable String doctrineShortc
2727
this.doctrineNamespace = doctrineNamespace;
2828
}
2929

30+
@Nullable
31+
public String getDoctrineShortcut() {
32+
return doctrineShortcut;
33+
}
34+
3035
@NotNull
3136
public PhpClass getPhpClass() {
3237
return phpClass;

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/util/PhpIndexUtilTest.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
44
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
55
import fr.adrienbrault.idea.symfony2plugin.util.PhpIndexUtil;
6-
import org.apache.commons.lang3.StringUtils;
76

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

32-
assertEquals(
33-
"\\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",
34-
StringUtils.join(foobar, ",")
30+
assertContainsElements(
31+
foobar,
32+
"\\Foobar\\Class1",
33+
"\\Foobar\\Class2",
34+
"\\Foobar\\Foobar2\\Foobar3\\Class1",
35+
"\\Foobar\\Foobar2\\Foobar3\\Class2",
36+
"\\Foobar\\Foobar2\\Foobar3\\Interface1",
37+
"\\Foobar\\Foobar2\\Foobar3\\Interface2",
38+
"\\Foobar\\Foobar2\\FoobarNot\\Class1",
39+
"\\Foobar\\Foobar2\\FoobarNot\\Class2",
40+
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1",
41+
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2",
42+
"\\Foobar\\Interface1",
43+
"\\Foobar\\Interface2"
3544
);
3645

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

42-
assertEquals(
43-
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1,\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2",
44-
StringUtils.join(foobar2, ",")
50+
assertContainsElements(
51+
foobar2,
52+
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class1",
53+
"\\Foobar\\Foobar2\\Foobar\\Foobar4\\Class2"
4554
);
4655
}
4756

0 commit comments

Comments
 (0)