Skip to content

Commit

Permalink
Merge pull request #2363 from Haehnchen/feature/model-wall-time
Browse files Browse the repository at this point in the history
optimize class namespace loading cache Doctrine models, to reduce wall time calling
  • Loading branch information
Haehnchen authored Apr 28, 2024
2 parents d27a256 + ddb89f9 commit d5d5e1a
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 38 deletions.
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

0 comments on commit d5d5e1a

Please sign in to comment.