From d008eea56c3d962d4b460933f075bc529aa2d5be Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Fri, 10 Apr 2020 12:34:20 +0200 Subject: [PATCH] provide better detection for translation directory inside based on the cached "translations" folder --- .../container/util/ServiceContainerUtil.java | 34 +---- .../translation/TranslationIndex.java | 143 +++++++++--------- .../parser/TranslationPsiParser.java | 46 +++--- .../util/TimeSecondModificationTracker.java | 38 +++++ 4 files changed, 131 insertions(+), 130 deletions(-) create mode 100644 src/main/java/fr/adrienbrault/idea/symfony2plugin/util/TimeSecondModificationTracker.java diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java index 573a03f21..6da47740d 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java @@ -32,6 +32,7 @@ import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; +import fr.adrienbrault.idea.symfony2plugin.util.TimeSecondModificationTracker; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil; import fr.adrienbrault.idea.symfony2plugin.util.psi.PsiElementAssertUtil; import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper; @@ -41,7 +42,6 @@ import org.jetbrains.yaml.YAMLUtil; import org.jetbrains.yaml.psi.*; -import java.time.Instant; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -62,8 +62,6 @@ public class ServiceContainerUtil { new MethodMatcher.CallToSignature("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController", "get"), }; - private static ModificationTracker TIMED_MODIFICATION_TRACKER = new TimeSecondModificationTracker(60); - private static final Key>> SYMFONY_COMPILED_TIMED_SERVICE_WATCHER = new Key<>("SYMFONY_COMPILED_TIMED_SERVICE_WATCHER"); private static final Key>> SYMFONY_COMPILED_SERVICE_WATCHER = new Key<>("SYMFONY_COMPILED_SERVICE_WATCHER"); @@ -640,34 +638,6 @@ public static List getSortedServiceId(@NotNull Project project, @NotNull return myIds; } - /** - * Provide a modification on nearest second value - */ - private static class TimeSecondModificationTracker implements ModificationTracker { - private final int expiresAfter; - - public TimeSecondModificationTracker(int expiresAfter) { - this.expiresAfter = expiresAfter; - } - - @Override - public long getModificationCount() { - long unixTime = Instant.now().getEpochSecond(); - return roundNearest(unixTime); - } - - private long roundNearest(long n) { - // Smaller multiple - long a = (n / this.expiresAfter) * this.expiresAfter; - - // Larger multiple - long b = a + this.expiresAfter; - - // Return of closest of two - return (n - a > b - n) ? b : a; - } - } - /** * Find compiled and cache it until any psi change occur * @@ -732,7 +702,7 @@ private static Collection getContainerFilesInner(@NotNull Project projec } } - return CachedValueProvider.Result.create(files, TIMED_MODIFICATION_TRACKER); + return CachedValueProvider.Result.create(files, TimeSecondModificationTracker.TIMED_MODIFICATION_TRACKER_60); }, false); } } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/TranslationIndex.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/TranslationIndex.java index 7d461bbee..229e2e632 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/TranslationIndex.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/TranslationIndex.java @@ -1,32 +1,45 @@ package fr.adrienbrault.idea.symfony2plugin.translation; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.util.CachedValue; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; import fr.adrienbrault.idea.symfony2plugin.Settings; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; +import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil; import fr.adrienbrault.idea.symfony2plugin.translation.parser.TranslationPsiParser; import fr.adrienbrault.idea.symfony2plugin.translation.parser.TranslationStringMap; +import fr.adrienbrault.idea.symfony2plugin.util.TimeSecondModificationTracker; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * @author Daniel Espendiller */ public class TranslationIndex { + private static final Key>> SYMFONY_TRANSLATION_COMPILED_TIMED_WATCHER = new Key<>("SYMFONY_TRANSLATION_COMPILED_TIMED_WATCHER"); + private static final Key>> SYMFONY_TRANSLATION_COMPILED = new Key<>("SYMFONY_TRANSLATION_COMPILED"); - protected static Map instance = new HashMap<>(); + private static Map instance = new HashMap<>(); - protected Project project; + private Project project; @Nullable private TranslationStringMap translationStringMap; - private Long translationStringMapModified; - - public static TranslationIndex getInstance(Project project){ + private long translationStringMapModified; + public static TranslationIndex getInstance(@NotNull Project project){ TranslationIndex projectInstance = instance.get(project); if(projectInstance != null) { return projectInstance; @@ -36,102 +49,92 @@ public static TranslationIndex getInstance(Project project){ instance.put(project, projectInstance); return projectInstance; - } - public TranslationIndex(Project project) { + private TranslationIndex(@NotNull Project project) { this.project = project; } - synchronized public TranslationStringMap getTranslationMap() { - if(this.translationStringMap != null && this.isCacheValid()) { return this.translationStringMap; } - File translationDirectory = this.getTranslationRoot(); - if(null == translationDirectory) { + Collection translationDirectories = this.getTranslationRoot(); + if(translationDirectories.size() == 0) { return new TranslationStringMap(); } - Symfony2ProjectComponent.getLogger().info("translations changed: " + translationDirectory.toString()); + Symfony2ProjectComponent.getLogger().info("translations changed: " + StringUtils.join(translationDirectories.stream().map(File::toString).collect(Collectors.toSet()), ",")); - this.translationStringMapModified = translationDirectory.lastModified(); - return this.translationStringMap = new TranslationPsiParser(project).parsePathMatcher(translationDirectory.getPath()); + this.translationStringMapModified = translationDirectories.stream().mapToLong(File::lastModified).sum(); + return this.translationStringMap = new TranslationPsiParser(project, translationDirectories).parsePathMatcher(); } - protected boolean isCacheValid() { - + private boolean isCacheValid() { // symfony2 recreates translation file on change, so folder modtime is caching indicator - File translationRootPath = this.getTranslationRoot(); - if (null == translationRootPath) { + Collection translationDirectories = this.getTranslationRoot(); + if(translationDirectories.size() == 0) { return false; } - Long translationModified = translationRootPath.lastModified(); - if(!translationModified.equals(translationStringMapModified)) { - return false; - } - - // @TODO make this more abstract - // we check for possible file modifications here per translation file - if(this.translationStringMap != null) { + return translationDirectories.stream().mapToLong(File::lastModified).sum() == translationStringMapModified; + } + @NotNull + private Collection getTranslationRoot() { + return CachedValuesManager.getManager(project) + .getCachedValue( + project, + SYMFONY_TRANSLATION_COMPILED, + () -> CachedValueProvider.Result.create(getTranslationRootInnerTime(), PsiModificationTracker.MODIFICATION_COUNT), + false + ); + } - File file = new File(translationRootPath.getPath()); + @NotNull + private Collection getTranslationRootInnerTime() { + return CachedValuesManager.getManager(project).getCachedValue(project, SYMFONY_TRANSLATION_COMPILED_TIMED_WATCHER, () -> CachedValueProvider.Result.create(getTranslationRootInner(), TimeSecondModificationTracker.TIMED_MODIFICATION_TRACKER_60), false); + } - // use cache in any i/o error - File[] files = file.listFiles(); - if(null == files) { - return true; - } + @NotNull + private Collection getTranslationRootInner() { + Collection files = new HashSet<>(); - // directory is empty or not exits, before and after instance - Map fileNames = this.translationStringMap.getFileNames(); - if(files.length == 0 && fileNames.size() == 0) { - return true; + String translationPath = Settings.getInstance(project).pathToTranslation; + if (StringUtils.isNotBlank(translationPath)) { + if (!FileUtil.isAbsolute(translationPath)) { + translationPath = project.getBasePath() + "/" + translationPath; } - for (File fileEntry : files) { - if (!fileEntry.isDirectory()) { - String fileName = fileEntry.getName(); - if(fileName.startsWith("catalogue") && fileName.endsWith("php")) { - - - if(!fileNames.containsKey(fileName)) { - return false; - } - - if(!fileNames.get(fileName).equals(fileEntry.lastModified())) { - return false; - } - - } - } + File file = new File(translationPath); + if(file.exists() && file.isDirectory()) { + files.add(file); } - - } - return true; - } + VirtualFile baseDir = project.getBaseDir(); - @Nullable - protected File getTranslationRoot() { + for (String containerFile : ServiceContainerUtil.getContainerFiles(project)) { + // resolve the file + VirtualFile containerVirtualFile = VfsUtil.findRelativeFile(containerFile, baseDir); + if (containerVirtualFile == null) { + continue; + } - String translationPath = Settings.getInstance(this.project).pathToTranslation; - if (!FileUtil.isAbsolute(translationPath)) { - translationPath = project.getBasePath() + "/" + translationPath; - } + // get directory of the file; translation folder is same directory + VirtualFile cacheDirectory = containerVirtualFile.getParent(); + if (cacheDirectory == null) { + continue; + } - File file = new File(translationPath); - if(!file.exists() || !file.isDirectory()) { - return null; + // get translation sub directory + VirtualFile translations = cacheDirectory.findChild("translations"); + if (translations != null) { + files.add(VfsUtilCore.virtualToIoFile(translations)); + } } - return file; + return files; } - - } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/parser/TranslationPsiParser.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/parser/TranslationPsiParser.java index bb6b8b283..3bb3fe303 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/parser/TranslationPsiParser.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/parser/TranslationPsiParser.java @@ -11,6 +11,7 @@ import com.jetbrains.php.lang.psi.elements.*; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -20,31 +21,31 @@ * @author Daniel Espendiller */ public class TranslationPsiParser { + @NotNull + private final Project project; - private Project project; + @NotNull + private final Collection paths; + + @NotNull final private TranslationStringMap translationStringMap; - public TranslationPsiParser(Project project) { + public TranslationPsiParser(@NotNull Project project, @NotNull Collection paths) { this.project = project; + this.paths = paths; this.translationStringMap = new TranslationStringMap(); } - public TranslationStringMap parsePathMatcher(String path) { - - File file = new File(path); - File[] files = file.listFiles(); - - if(null == files) { - return this.translationStringMap; - } + public TranslationStringMap parsePathMatcher() { + for (File path : paths) { + File[] files = path.listFiles((directory, s) -> s.startsWith("catalogue") && s.endsWith("php")); + if(null == files || files.length == 0) { + continue; + } - for (final File fileEntry : files) { - if (!fileEntry.isDirectory()) { - String fileName = fileEntry.getName(); - if(fileName.startsWith("catalogue") && fileName.endsWith("php")) { - this.parse(fileEntry); - this.translationStringMap.addFile(fileName, fileEntry.lastModified()); - } + for (final File fileEntry : files) { + this.parse(fileEntry); + this.translationStringMap.addFile(fileEntry.getName(), fileEntry.lastModified()); } } @@ -52,7 +53,6 @@ public TranslationStringMap parsePathMatcher(String path) { } public void parse(File file) { - VirtualFile virtualFile = VfsUtil.findFileByIoFile(file, true); if(virtualFile == null) { Symfony2ProjectComponent.getLogger().info("VfsUtil missing translation: " + file.getPath()); @@ -82,17 +82,12 @@ public void parse(File file) { if(phpClass != null && PhpElementsUtil.isInstanceOf(phpClass, "\\Symfony\\Component\\Translation\\MessageCatalogueInterface")) { this.getTranslationMessages(newExpression); } - } - } - } - } private void getTranslationMessages(NewExpression newExpression) { - // first parameter hold our huge translation arrays PsiElement[] parameters = newExpression.getParameters(); if(parameters.length < 2 || !(parameters[1] instanceof ArrayCreationExpression)) { @@ -111,11 +106,8 @@ private void getTranslationMessages(NewExpression newExpression) { if(arrayValue instanceof ArrayCreationExpression) { getTransKeys(transDomain, (ArrayCreationExpression) arrayValue); } - } - } - } private void getTransKeys(String domain, ArrayCreationExpression translationArray) { @@ -127,7 +119,5 @@ private void getTransKeys(String domain, ArrayCreationExpression translationArra this.translationStringMap.addString(domain, transKey); } } - } - } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/TimeSecondModificationTracker.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/TimeSecondModificationTracker.java new file mode 100644 index 000000000..1b8731d3c --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/TimeSecondModificationTracker.java @@ -0,0 +1,38 @@ +package fr.adrienbrault.idea.symfony2plugin.util; + +import com.intellij.openapi.util.ModificationTracker; + +import java.time.Instant; + +/** + * Provide a modification on nearest second value + * + * @author Daniel Espendiller + */ +public class TimeSecondModificationTracker implements ModificationTracker { + + public static ModificationTracker TIMED_MODIFICATION_TRACKER_60 = new TimeSecondModificationTracker(60); + + private final int expiresAfter; + + public TimeSecondModificationTracker(int expiresAfter) { + this.expiresAfter = expiresAfter; + } + + @Override + public long getModificationCount() { + long unixTime = Instant.now().getEpochSecond(); + return roundNearest(unixTime); + } + + private long roundNearest(long n) { + // Smaller multiple + long a = (n / this.expiresAfter) * this.expiresAfter; + + // Larger multiple + long b = a + this.expiresAfter; + + // Return of closest of two + return (n - a > b - n) ? b : a; + } +}