Skip to content

provide better detection for translation directory inside based on the cached "translations" folder #1443

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

Merged
merged 1 commit into from
Apr 10, 2020
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 @@ -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;
Expand All @@ -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;
Expand All @@ -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<CachedValue<Collection<String>>> SYMFONY_COMPILED_TIMED_SERVICE_WATCHER = new Key<>("SYMFONY_COMPILED_TIMED_SERVICE_WATCHER");
private static final Key<CachedValue<Collection<String>>> SYMFONY_COMPILED_SERVICE_WATCHER = new Key<>("SYMFONY_COMPILED_SERVICE_WATCHER");

Expand Down Expand Up @@ -640,34 +638,6 @@ public static List<String> 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
*
Expand Down Expand Up @@ -732,7 +702,7 @@ private static Collection<String> getContainerFilesInner(@NotNull Project projec
}
}

return CachedValueProvider.Result.create(files, TIMED_MODIFICATION_TRACKER);
return CachedValueProvider.Result.create(files, TimeSecondModificationTracker.TIMED_MODIFICATION_TRACKER_60);
}, false);
}
}
Original file line number Diff line number Diff line change
@@ -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 <daniel@espendiller.net>
*/
public class TranslationIndex {
private static final Key<CachedValue<Collection<File>>> SYMFONY_TRANSLATION_COMPILED_TIMED_WATCHER = new Key<>("SYMFONY_TRANSLATION_COMPILED_TIMED_WATCHER");
private static final Key<CachedValue<Collection<File>>> SYMFONY_TRANSLATION_COMPILED = new Key<>("SYMFONY_TRANSLATION_COMPILED");

protected static Map<Project, TranslationIndex> instance = new HashMap<>();
private static Map<Project, TranslationIndex> 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;
Expand All @@ -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<File> 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<File> 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<File> 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<File> 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<File> getTranslationRootInner() {
Collection<File> files = new HashSet<>();

// directory is empty or not exits, before and after instance
Map<String, Long> 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;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,39 +21,38 @@
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class TranslationPsiParser {
@NotNull
private final Project project;

private Project project;
@NotNull
private final Collection<File> paths;

@NotNull final
private TranslationStringMap translationStringMap;

public TranslationPsiParser(Project project) {
public TranslationPsiParser(@NotNull Project project, @NotNull Collection<File> 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());
}
}

return this.translationStringMap;
}

public void parse(File file) {

VirtualFile virtualFile = VfsUtil.findFileByIoFile(file, true);
if(virtualFile == null) {
Symfony2ProjectComponent.getLogger().info("VfsUtil missing translation: " + file.getPath());
Expand Down Expand Up @@ -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)) {
Expand All @@ -111,11 +106,8 @@ private void getTranslationMessages(NewExpression newExpression) {
if(arrayValue instanceof ArrayCreationExpression) {
getTransKeys(transDomain, (ArrayCreationExpression) arrayValue);
}

}

}

}

private void getTransKeys(String domain, ArrayCreationExpression translationArray) {
Expand All @@ -127,7 +119,5 @@ private void getTransKeys(String domain, ArrayCreationExpression translationArra
this.translationStringMap.addString(domain, transKey);
}
}

}

}
Loading