You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BundleSynchronizedMatcher.java 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.test.i18n;
  21. import java.io.File;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.OutputStreamWriter;
  26. import java.io.Writer;
  27. import java.nio.charset.StandardCharsets;
  28. import java.util.Map;
  29. import java.util.Properties;
  30. import java.util.SortedMap;
  31. import java.util.TreeMap;
  32. import org.apache.commons.io.IOUtils;
  33. import org.hamcrest.BaseMatcher;
  34. import org.hamcrest.Description;
  35. import static org.junit.Assert.assertNotNull;
  36. import static org.junit.Assert.assertTrue;
  37. import static org.junit.Assert.fail;
  38. public class BundleSynchronizedMatcher extends BaseMatcher<String> {
  39. public static final String L10N_PATH = "/org/sonar/l10n/";
  40. private String bundleName;
  41. private SortedMap<String, String> missingKeys;
  42. private SortedMap<String, String> additionalKeys;
  43. @Override
  44. public boolean matches(Object arg0) {
  45. if (!(arg0 instanceof String)) {
  46. return false;
  47. }
  48. bundleName = (String) arg0;
  49. // Find the bundle that needs to be verified
  50. InputStream bundleInputStream = getBundleFileInputStream(bundleName);
  51. // Find the default bundle which the provided one should be compared to
  52. InputStream defaultBundleInputStream = getDefaultBundleFileInputStream(bundleName);
  53. // and now let's compare!
  54. try {
  55. // search for missing keys
  56. missingKeys = retrieveMissingTranslations(bundleInputStream, defaultBundleInputStream);
  57. // and now for additional keys
  58. bundleInputStream = getBundleFileInputStream(bundleName);
  59. defaultBundleInputStream = getDefaultBundleFileInputStream(bundleName);
  60. additionalKeys = retrieveMissingTranslations(defaultBundleInputStream, bundleInputStream);
  61. // And fail only if there are missing keys
  62. return missingKeys.isEmpty();
  63. } catch (IOException e) {
  64. fail("An error occurred while reading the bundles: " + e.getMessage());
  65. return false;
  66. } finally {
  67. IOUtils.closeQuietly(bundleInputStream);
  68. IOUtils.closeQuietly(defaultBundleInputStream);
  69. }
  70. }
  71. @Override
  72. public void describeTo(Description description) {
  73. // report file
  74. File dumpFile = new File("build/l10n/" + bundleName + ".report.txt");
  75. // prepare message
  76. StringBuilder details = prepareDetailsMessage(dumpFile);
  77. description.appendText(details.toString());
  78. // print report in target directory
  79. printReport(dumpFile, details.toString());
  80. }
  81. private StringBuilder prepareDetailsMessage(File dumpFile) {
  82. StringBuilder details = new StringBuilder("\n=======================\n'");
  83. details.append(bundleName);
  84. details.append("' is not up-to-date.");
  85. print("\n\n Missing translations are:", missingKeys, details);
  86. print("\n\nThe following translations do not exist in the reference bundle:", additionalKeys, details);
  87. details.append("\n\nSee report file located at: ");
  88. details.append(dumpFile.getAbsolutePath());
  89. details.append("\n=======================");
  90. return details;
  91. }
  92. private void print(String title, SortedMap<String, String> translations, StringBuilder to) {
  93. if (!translations.isEmpty()) {
  94. to.append(title);
  95. for (Map.Entry<String, String> entry : translations.entrySet()) {
  96. to.append("\n").append(entry.getKey()).append("=").append(entry.getValue());
  97. }
  98. }
  99. }
  100. private void printReport(File dumpFile, String details) {
  101. if (dumpFile.exists()) {
  102. dumpFile.delete();
  103. }
  104. dumpFile.getParentFile().mkdirs();
  105. try (Writer writer = new OutputStreamWriter(new FileOutputStream(dumpFile), StandardCharsets.UTF_8)) {
  106. writer.write(details);
  107. } catch (IOException e) {
  108. throw new IllegalStateException("Unable to write the report to 'target/l10n/" + bundleName + ".report.txt'", e);
  109. }
  110. }
  111. protected static SortedMap<String, String> retrieveMissingTranslations(InputStream bundle, InputStream referenceBundle) throws IOException {
  112. SortedMap<String, String> missingKeys = new TreeMap<>();
  113. Properties bundleProps = loadProperties(bundle);
  114. Properties referenceProperties = loadProperties(referenceBundle);
  115. for (Map.Entry<Object, Object> entry : referenceProperties.entrySet()) {
  116. String key = (String) entry.getKey();
  117. if (!bundleProps.containsKey(key)) {
  118. missingKeys.put(key, (String) entry.getValue());
  119. }
  120. }
  121. return missingKeys;
  122. }
  123. protected static Properties loadProperties(InputStream inputStream) throws IOException {
  124. Properties props = new Properties();
  125. props.load(inputStream);
  126. return props;
  127. }
  128. protected static InputStream getBundleFileInputStream(String bundleName) {
  129. InputStream bundle = BundleSynchronizedMatcher.class.getResourceAsStream(L10N_PATH + bundleName);
  130. assertNotNull("File '" + bundleName + "' does not exist in '/org/sonar/l10n/'.", bundle);
  131. return bundle;
  132. }
  133. protected static InputStream getDefaultBundleFileInputStream(String bundleName) {
  134. String defaultBundleName = extractDefaultBundleName(bundleName);
  135. InputStream bundle = BundleSynchronizedMatcher.class.getResourceAsStream(L10N_PATH + defaultBundleName);
  136. assertNotNull("Default bundle '" + defaultBundleName + "' could not be found: add a dependency to the corresponding plugin in your POM.", bundle);
  137. return bundle;
  138. }
  139. protected static String extractDefaultBundleName(String bundleName) {
  140. int firstUnderScoreIndex = bundleName.indexOf('_');
  141. assertTrue("The bundle '" + bundleName + "' is a default bundle (without locale), so it can't be compared.", firstUnderScoreIndex > 0);
  142. return bundleName.substring(0, firstUnderScoreIndex) + ".properties";
  143. }
  144. }