001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.test.i18n;
021
022 import org.apache.commons.io.IOUtils;
023 import org.hamcrest.BaseMatcher;
024 import org.hamcrest.Description;
025
026 import java.io.File;
027 import java.io.FileWriter;
028 import java.io.IOException;
029 import java.io.InputStream;
030 import java.util.Map;
031 import java.util.Properties;
032 import java.util.SortedMap;
033 import java.util.TreeMap;
034
035 import static org.hamcrest.Matchers.is;
036 import static org.hamcrest.Matchers.notNullValue;
037 import static org.junit.Assert.assertThat;
038 import static org.junit.Assert.fail;
039
040 public class BundleSynchronizedMatcher extends BaseMatcher<String> {
041
042 public static final String L10N_PATH = "/org/sonar/l10n/";
043
044 private String bundleName;
045 private SortedMap<String, String> missingKeys;
046 private SortedMap<String, String> additionalKeys;
047
048 @Override
049 public boolean matches(Object arg0) {
050 if (!(arg0 instanceof String)) {
051 return false;
052 }
053 bundleName = (String) arg0;
054
055 // Find the bundle that needs to be verified
056 InputStream bundleInputStream = getBundleFileInputStream(bundleName);
057
058 // Find the default bundle which the provided one should be compared to
059 InputStream defaultBundleInputStream = getDefaultBundleFileInputStream(bundleName);
060
061 // and now let's compare!
062 try {
063 // search for missing keys
064 missingKeys = retrieveMissingTranslations(bundleInputStream, defaultBundleInputStream);
065
066 // and now for additional keys
067 bundleInputStream = getBundleFileInputStream(bundleName);
068 defaultBundleInputStream = getDefaultBundleFileInputStream(bundleName);
069 additionalKeys = retrieveMissingTranslations(defaultBundleInputStream, bundleInputStream);
070
071 // And fail only if there are missing keys
072 return missingKeys.isEmpty();
073 } catch (IOException e) {
074 fail("An error occured while reading the bundles: " + e.getMessage());
075 return false;
076 } finally {
077 IOUtils.closeQuietly(bundleInputStream);
078 IOUtils.closeQuietly(defaultBundleInputStream);
079 }
080 }
081
082 @Override
083 public void describeTo(Description description) {
084 // report file
085 File dumpFile = new File("target/l10n/" + bundleName + ".report.txt");
086
087 // prepare message
088 StringBuilder details = prepareDetailsMessage(dumpFile);
089 description.appendText(details.toString());
090
091 // print report in target directory
092 printReport(dumpFile, details.toString());
093 }
094
095 private StringBuilder prepareDetailsMessage(File dumpFile) {
096 StringBuilder details = new StringBuilder("\n=======================\n'");
097 details.append(bundleName);
098 details.append("' is not up-to-date.");
099 print("\n\n Missing translations are:", missingKeys, details);
100 print("\n\nThe following translations do not exist in the reference bundle:", additionalKeys, details);
101 details.append("\n\nSee report file located at: ");
102 details.append(dumpFile.getAbsolutePath());
103 details.append("\n=======================");
104 return details;
105 }
106
107 private void print(String title, SortedMap<String, String> translations, StringBuilder to) {
108 if (!translations.isEmpty()) {
109 to.append(title);
110 for (Map.Entry<String, String> entry : translations.entrySet()) {
111 to.append("\n").append(entry.getKey()).append("=").append(entry.getValue());
112 }
113 }
114 }
115
116 private void printReport(File dumpFile, String details) {
117 if (dumpFile.exists()) {
118 dumpFile.delete();
119 }
120 dumpFile.getParentFile().mkdirs();
121 FileWriter writer = null;
122 try {
123 writer = new FileWriter(dumpFile);
124 writer.write(details);
125 } catch (IOException e) {
126 throw new IllegalStateException("Unable to write the report to 'target/l10n/" + bundleName + ".report.txt'", e);
127 } finally {
128 IOUtils.closeQuietly(writer);
129 }
130 }
131
132 protected static SortedMap<String, String> retrieveMissingTranslations(InputStream bundle, InputStream referenceBundle) throws IOException {
133 SortedMap<String, String> missingKeys = new TreeMap();
134
135 Properties bundleProps = loadProperties(bundle);
136 Properties referenceProperties = loadProperties(referenceBundle);
137
138 for (Map.Entry<Object, Object> entry : referenceProperties.entrySet()) {
139 String key = (String) entry.getKey();
140 if (!bundleProps.containsKey(key)) {
141 missingKeys.put(key, (String) entry.getValue());
142 }
143 }
144
145 return missingKeys;
146 }
147
148 protected static Properties loadProperties(InputStream inputStream) throws IOException {
149 Properties props = new Properties();
150 props.load(inputStream);
151 return props;
152 }
153
154 protected static InputStream getBundleFileInputStream(String bundleName) {
155 InputStream bundle = BundleSynchronizedMatcher.class.getResourceAsStream(L10N_PATH + bundleName);
156 assertThat("File '" + bundleName + "' does not exist in '/org/sonar/l10n/'.", bundle, notNullValue());
157 return bundle;
158 }
159
160 protected static InputStream getDefaultBundleFileInputStream(String bundleName) {
161 String defaultBundleName = extractDefaultBundleName(bundleName);
162 InputStream bundle = BundleSynchronizedMatcher.class.getResourceAsStream(L10N_PATH + defaultBundleName);
163 assertThat("Default bundle '" + defaultBundleName + "' could not be found: add a dependency to the corresponding plugin in your POM.", bundle, notNullValue());
164 return bundle;
165 }
166
167 protected static String extractDefaultBundleName(String bundleName) {
168 int firstUnderScoreIndex = bundleName.indexOf('_');
169 assertThat("The bundle '" + bundleName + "' is a default bundle (without locale), so it can't be compared.", firstUnderScoreIndex > 0,
170 is(true));
171 return bundleName.substring(0, firstUnderScoreIndex) + ".properties";
172 }
173
174 }