Fix location bundle with fast access#3327
Conversation
…ed, that all accesses to it are only lookups in hashmaps Made all access of Location-messages a fast hash table lookup. Fixed one message in JabRefFrame that used an escaped key instead of the unescaped version.
* Fix NPE in MainTable * Fix build
| public static String lang(String key, String... params) { | ||
| if (localizedMessages == null) { | ||
| // I'm logging this because it should never happen | ||
| LOGGER.error("Messages are not initialized in " + Localization.class); |
There was a problem hiding this comment.
Yozu can omit the class part when writing a log message, as the Logger is already instantiated for the current class and therefore knows its name.
| */ | ||
| private static Hashtable<String, String> createLookupTable(ResourceBundle baseBundle){ | ||
| final ArrayList<String> baseKeys = Collections.list(baseBundle.getKeys()); | ||
| Hashtable<String, String> lookup = new Hashtable<>(baseKeys.size()); |
There was a problem hiding this comment.
HashTable is deprecated, better use HashMap.
And with java 8 streams and the Collection methods you an even make this code a lot nicer:
https://www.mkyong.com/java8/java-8-convert-list-to-map/
| protected static String translate(ResourceBundle resBundle, String idForErrorMessage, String key, String... params) { | ||
| Objects.requireNonNull(resBundle); | ||
| private static String lookup(LocalizationBundle bundle, String idForErrorMessage, String key, String... params) { | ||
| if (key == null) { |
There was a problem hiding this comment.
Better use the Objects.requireNonNull here. It will automatically throw an NPE
| private static class LocalizationBundle extends ResourceBundle { | ||
|
|
||
| // I'm using a Hashtable since it allows to get the keys directly as an Enumeration | ||
| private Hashtable<String, String> lookup; |
There was a problem hiding this comment.
As mentioned above, use as HashMap
Siedlerchr
left a comment
There was a problem hiding this comment.
First of all thanks for the analysis and for providing a fix!
I added some minor code wise suggestions, but in general looks good
lenhard
left a comment
There was a problem hiding this comment.
I have added a few minor comments on top of those of @Siedlerchr, but overall the code looks good.
I have also played around with a running JabRef, changing the localization etc., in this branch and did not notice errors.
I think your refactoring of the localization classes is fine, as well as moving the initialization into the preferences. So, when the few minor things are fixed, this PR is good to go.
| ResourceBundle menuTitles = ResourceBundle.getBundle(MENU_RESOURCE_PREFIX, locale, new EncodingControl(StandardCharsets.UTF_8)); | ||
| // Just for the case something really stupid happened. | ||
| if (messages == null || menuTitles == null) { | ||
| LOGGER.error("Could not load language translation files in " + Localization.class); |
There was a problem hiding this comment.
Same remark regarding the class files as by @Siedlerchr above
| // Just for the case something really stupid happened. | ||
| if (messages == null || menuTitles == null) { | ||
| LOGGER.error("Could not load language translation files in " + Localization.class); | ||
| throw new NullPointerException(); |
There was a problem hiding this comment.
I think throwing an IllegalStateException would be better here. Just for the more descriptive exception type.
There was a problem hiding this comment.
I used
Objects.requireNonNull(messages, "Could not load " + RESOURCE_PREFIX + " resource.");
Objects.requireNonNull(menuTitles, "Could not load " + MENU_RESOURCE_PREFIX + " resource.");
which throws NPE, but gives a description.
| if (menuTitles == null) { | ||
| setLanguage("en"); | ||
| public final Object handleGetObject(String key) { | ||
| if (key == null) { |
There was a problem hiding this comment.
As above, rather use Objects.requireNonNull here.
General code cleanup
|
I fixed the issues. Is there a nicer way to create a |
|
Unfortunately there is no simpler solution for the Map thing. |
Replacement of last null test with Objects.requireNonNull
|
Since the checkstyle problems are fixed, I'll merge this now. Thanks a lot for your contribution! |
* upstream/master: Initializing EntryEditor Tabs on focus (#3331) Fix #3292: annotations are now automatically refreshed (#3325) Change integrity message for names depending on database mode (#3330) Fix location bundle with fast access (#3327) Small code cleanup Fix "path not found" exception Small fix in drag and drop handler of linked files (#3328) # Conflicts: # src/main/java/org/jabref/gui/DefaultInjector.java
* upstream/master: (31 commits) Source tab entry duplication (#3360) Use CITE_COMMENT not only for external latex editors but also for cop… (#3351) Updating with new translations (#3348) Upgrade error-prone (#3350) Jabref_pt_BR partially updated (#3349) Used late initialization for context menus (#3340) Fix NPE when calling with bib file as cmd argument (#3343) update mockito-core from 2.10.0 -> 2.11.0 (#3338) Remove underscore in Localized messages (#3336) Localisation: French: new entries translated (#3337) Refactoring: Lazy init of all editor tabs (#3333) Initializing EntryEditor Tabs on focus (#3331) Fix #3292: annotations are now automatically refreshed (#3325) Change integrity message for names depending on database mode (#3330) Fix location bundle with fast access (#3327) Small code cleanup Fix "path not found" exception Small fix in drag and drop handler of linked files (#3328) Fix NPE in MainTable (#3318) Increase relative size of abstract field in editor (#3320) ...
This PR addresses the issue I mentioned under Localization memory. To provide a reasonable implementation and not only a bugfix I had to change the layout. These things bothered me the most:
LocalizationBundleandLocalizationwere calling each other. We neededLocalizationto create the keys in the bundle and on the other hand, theLocalizationBundlewas only used in theLocalizationclassLocalizationseemed like a quick fix to provide messages as bundle to Java FXTo unify the implementation, I based the complete
Localizationframework on two instances (menus and messages) ofLocalizationBundleand made this an internal class as it should not be used without it. The bundles are (re)created whensetLanguageis called and contain the unescaped keys and messages for the specified language as a fast hash table.This enables two things: (1) We can pass the
LocalizationBundleinstance directly to Java FX withgetMessagesand since we only pass the reference to the hash table, it will be instantaneous. (2) Every call tolangormenuTitleis a simple lookup in the hash table and a replacement of possible parameters in the string.The public API for
Localization, meaninglang,menuTitle,getMessagesandsetLanguagedid not change and works as expected. During profiling/debugging I noticed, however, that there is a flaw in the JabRef startup. The language is set after the initialization of the preferences. At least one preference setting uses a localized string in a static field. Since when uninitialized, theLocalizationreverts back to "en", these strings will never be in the user language (see this issue). Furthermore, due to this access, theLocalizationframework will always be initialized several times. Once in the init of the preferences and once further down inmain.This can only be fixed by pushing
setLanguageto the initialization of the preferences which I did. One additional minor thing was that one message key was used escaped and had to be unescaped. Other than that, this implementation worked out of the box and passed all existing localization tests.I commented my code heavily and would be glad if someone would review it. Don't forget that I'm looking only about 5 days on the code base and it is very likely I have overlooked something.