From 150f40d395f9f129167c37f37a7fdbfcc6170ac6 Mon Sep 17 00:00:00 2001 From: marhali Date: Fri, 14 Oct 2022 17:36:01 +0200 Subject: [PATCH] improve exception handling on syntax errors Resolves #167 Resolves #177 --- CHANGELOG.md | 3 ++ .../easyi18n/action/OpenFileAction.java | 37 +++++++++++++++++++ .../easyi18n/action/SettingsAction.java | 6 ++- .../easyi18n/exception/SyntaxException.java | 20 ++++++++++ .../de/marhali/easyi18n/io/IOHandler.java | 4 ++ .../io/parser/json/JsonParserStrategy.java | 11 +++++- .../io/parser/json5/Json5ParserStrategy.java | 16 +++++++- .../properties/PropertiesParserStrategy.java | 14 ++++++- .../io/parser/yaml/YamlParserStrategy.java | 11 +++++- .../easyi18n/util/NotificationHelper.java | 21 ++++++++++- src/main/resources/messages.properties | 4 +- 11 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 src/main/java/de/marhali/easyi18n/action/OpenFileAction.java create mode 100644 src/main/java/de/marhali/easyi18n/exception/SyntaxException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d4d141..6a8d710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ # easy-i18n Changelog ## [Unreleased] +### Changed +- Improved exception handling on syntax errors + ### Fixed - Some settings are not retained on IDE restarts diff --git a/src/main/java/de/marhali/easyi18n/action/OpenFileAction.java b/src/main/java/de/marhali/easyi18n/action/OpenFileAction.java new file mode 100644 index 0000000..74e093d --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/action/OpenFileAction.java @@ -0,0 +1,37 @@ +package de.marhali.easyi18n.action; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.ResourceBundle; + +/** + * Plugin action to open a specific file. + * @author marhali + */ +public class OpenFileAction extends AnAction { + private final VirtualFile file; + + public OpenFileAction(VirtualFile file) { + this(file, true); + } + + public OpenFileAction(VirtualFile file, boolean showIcon) { + super(ResourceBundle.getBundle("messages").getString("action.file"), + null, showIcon ? AllIcons.FileTypes.Any_type : null); + this.file = file; + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = Objects.requireNonNull(e.getProject()); + FileEditorManager.getInstance(project).openFile(file, true, true); + } +} diff --git a/src/main/java/de/marhali/easyi18n/action/SettingsAction.java b/src/main/java/de/marhali/easyi18n/action/SettingsAction.java index fabd792..6ae2a30 100644 --- a/src/main/java/de/marhali/easyi18n/action/SettingsAction.java +++ b/src/main/java/de/marhali/easyi18n/action/SettingsAction.java @@ -18,8 +18,12 @@ import java.util.ResourceBundle; public class SettingsAction extends AnAction { public SettingsAction() { + this(true); + } + + public SettingsAction(boolean showIcon) { super(ResourceBundle.getBundle("messages").getString("action.settings"), - null, AllIcons.General.Settings); + null, showIcon ? AllIcons.General.Settings : null); } @Override diff --git a/src/main/java/de/marhali/easyi18n/exception/SyntaxException.java b/src/main/java/de/marhali/easyi18n/exception/SyntaxException.java new file mode 100644 index 0000000..59f0e0c --- /dev/null +++ b/src/main/java/de/marhali/easyi18n/exception/SyntaxException.java @@ -0,0 +1,20 @@ +package de.marhali.easyi18n.exception; + +import de.marhali.easyi18n.model.TranslationFile; + +/** + * Indicates a syntax error in a managed translation file. + * @author marhali + */ +public class SyntaxException extends RuntimeException { + private final TranslationFile file; + + public SyntaxException(String message, TranslationFile file) { + super(message); + this.file = file; + } + + public TranslationFile getFile() { + return file; + } +} diff --git a/src/main/java/de/marhali/easyi18n/io/IOHandler.java b/src/main/java/de/marhali/easyi18n/io/IOHandler.java index f2a8ee1..d77ac1f 100644 --- a/src/main/java/de/marhali/easyi18n/io/IOHandler.java +++ b/src/main/java/de/marhali/easyi18n/io/IOHandler.java @@ -10,12 +10,14 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import de.marhali.easyi18n.exception.EmptyLocalesDirException; +import de.marhali.easyi18n.exception.SyntaxException; import de.marhali.easyi18n.io.folder.FolderStrategy; import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.io.parser.ParserStrategyType; import de.marhali.easyi18n.model.*; import de.marhali.easyi18n.settings.ProjectSettings; +import de.marhali.easyi18n.util.NotificationHelper; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -73,6 +75,8 @@ public class IOHandler { for(TranslationFile file : translationFiles) { try { this.parserStrategy.read(file, data); + } catch (SyntaxException ex) { + NotificationHelper.createBadSyntaxNotification(project, ex); } catch(Exception ex) { throw new IOException(file + "\n\n" + ex.getMessage(), ex); } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java index 27fc51d..d9a2607 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/json/JsonParserStrategy.java @@ -4,8 +4,10 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; import com.intellij.openapi.vfs.VirtualFile; +import de.marhali.easyi18n.exception.SyntaxException; import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.model.*; import de.marhali.easyi18n.settings.ProjectSettings; @@ -36,7 +38,14 @@ public class JsonParserStrategy extends ParserStrategy { TranslationNode targetNode = super.getOrCreateTargetNode(file, data); try(Reader reader = new InputStreamReader(vf.getInputStream(), vf.getCharset())) { - JsonObject input = GSON.fromJson(reader, JsonObject.class); + JsonObject input; + + try { + input = GSON.fromJson(reader, JsonObject.class); + } catch (JsonSyntaxException ex) { + throw new SyntaxException(ex.getMessage(), file); + } + if(input != null) { // @input is null if file is completely empty JsonMapper.read(file.getLocale(), input, targetNode); } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java index 268bbf3..1baf0ec 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/json5/Json5ParserStrategy.java @@ -2,6 +2,7 @@ package de.marhali.easyi18n.io.parser.json5; import com.intellij.openapi.vfs.VirtualFile; +import de.marhali.easyi18n.exception.SyntaxException; import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; @@ -11,6 +12,7 @@ import de.marhali.json5.Json5; import de.marhali.json5.Json5Element; import de.marhali.json5.Json5Object; +import de.marhali.json5.exception.Json5Exception; import org.jetbrains.annotations.NotNull; import java.io.InputStreamReader; @@ -38,8 +40,18 @@ public class Json5ParserStrategy extends ParserStrategy { TranslationNode targetNode = super.getOrCreateTargetNode(file, data); try (Reader reader = new InputStreamReader(vf.getInputStream(), vf.getCharset())) { - Json5Element input = JSON5.parse(reader); - if(input != null && input.isJson5Object()) { + Json5Element input; + + try { + input = JSON5.parse(reader); + if(input != null && !input.isJson5Object()) { + throw new SyntaxException("Expected a json5 object as root node", file); + } + } catch (Json5Exception ex) { + throw new SyntaxException(ex.getMessage(), file); + } + + if(input != null) { Json5Mapper.read(file.getLocale(), input.getAsJson5Object(), targetNode); } } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java index 82b5832..936dbf8 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/properties/PropertiesParserStrategy.java @@ -2,6 +2,7 @@ package de.marhali.easyi18n.io.parser.properties; import com.intellij.openapi.vfs.VirtualFile; +import de.marhali.easyi18n.exception.SyntaxException; import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; @@ -11,6 +12,7 @@ import de.marhali.easyi18n.util.KeyPathConverter; import org.jetbrains.annotations.NotNull; +import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; @@ -38,8 +40,16 @@ public class PropertiesParserStrategy extends ParserStrategy { try(Reader reader = new InputStreamReader(vf.getInputStream(), vf.getCharset())) { SortableProperties input = new SortableProperties(this.settings.isSorting()); - input.load(reader); - PropertiesMapper.read(file.getLocale(), input, targetData, converter); + + try { + input.load(reader); + } catch(IOException ex) { + throw new SyntaxException(ex.getMessage(), file); + } + + if(!input.isEmpty()) { + PropertiesMapper.read(file.getLocale(), input, targetData, converter); + } } } diff --git a/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java b/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java index 97b21a3..8944255 100644 --- a/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java +++ b/src/main/java/de/marhali/easyi18n/io/parser/yaml/YamlParserStrategy.java @@ -2,6 +2,7 @@ package de.marhali.easyi18n.io.parser.yaml; import com.intellij.openapi.vfs.VirtualFile; +import de.marhali.easyi18n.exception.SyntaxException; import de.marhali.easyi18n.io.parser.ParserStrategy; import de.marhali.easyi18n.model.TranslationData; import de.marhali.easyi18n.model.TranslationFile; @@ -10,6 +11,7 @@ import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; +import org.yaml.snakeyaml.error.YAMLException; import thito.nodeflow.config.MapSection; import thito.nodeflow.config.Section; @@ -34,7 +36,14 @@ public class YamlParserStrategy extends ParserStrategy { TranslationNode targetNode = super.getOrCreateTargetNode(file, data); try(Reader reader = new InputStreamReader(vf.getInputStream(), vf.getCharset())) { - Section input = Section.parseToMap(reader); + Section input; + + try { + input = Section.parseToMap(reader); + } catch (YAMLException ex) { + throw new SyntaxException(ex.getMessage(), file); + } + YamlMapper.read(file.getLocale(), input, targetNode); } } diff --git a/src/main/java/de/marhali/easyi18n/util/NotificationHelper.java b/src/main/java/de/marhali/easyi18n/util/NotificationHelper.java index ea1393c..d969e8d 100644 --- a/src/main/java/de/marhali/easyi18n/util/NotificationHelper.java +++ b/src/main/java/de/marhali/easyi18n/util/NotificationHelper.java @@ -3,7 +3,9 @@ package de.marhali.easyi18n.util; import com.intellij.notification.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import de.marhali.easyi18n.action.OpenFileAction; import de.marhali.easyi18n.action.SettingsAction; +import de.marhali.easyi18n.exception.SyntaxException; import de.marhali.easyi18n.io.IOHandler; import de.marhali.easyi18n.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; @@ -16,6 +18,7 @@ import java.util.ResourceBundle; * @author marhali */ public class NotificationHelper { + private static final String NOTIFICATION_GROUP = "Easy I18n Notification Group"; public static void createIOError(@NotNull ProjectSettings state, Exception ex) { ResourceBundle bundle = ResourceBundle.getBundle("messages"); @@ -26,11 +29,27 @@ public class NotificationHelper { Logger.getInstance(IOHandler.class).error(message, ex); } + public static void createBadSyntaxNotification(Project project, SyntaxException ex) { + ResourceBundle bundle = ResourceBundle.getBundle("messages"); + + Notification notification = new Notification( + NOTIFICATION_GROUP, + bundle.getString("warning.bad-syntax"), + ex.getMessage(), + NotificationType.ERROR + ); + + notification.addAction(new OpenFileAction(ex.getFile().getVirtualFile(), false)); + notification.addAction(new SettingsAction(false)); + + Notifications.Bus.notify(notification, project); + } + public static void createEmptyLocalesDirNotification(Project project) { ResourceBundle bundle = ResourceBundle.getBundle("messages"); Notification notification = new Notification( - "Easy I18n Notification Group", + NOTIFICATION_GROUP, "Easy I18n", bundle.getString("warning.missing-config"), NotificationType.WARNING); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 40ec0c2..9d46c0e 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -13,6 +13,7 @@ action.settings=Settings action.search=Search... action.delete=Delete action.extract=Extract translation +action.file=Open file translation.key=Key translation.locales=Locales # Settings @@ -64,4 +65,5 @@ error.io=An error occurred while processing translation files. \n\ Path: {3} \n\ Please check examples at https://github.com/marhali/easy-i18n before reporting an issue! error.submit=Open Issue -warning.missing-config=Configure your local project structure \ No newline at end of file +warning.missing-config=Configure your local project structure +warning.bad-syntax=Syntax error in translation file \ No newline at end of file