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 }