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 public boolean matches(Object arg0) { 049 if (!(arg0 instanceof String)) { 050 return false; 051 } 052 bundleName = (String) arg0; 053 054 // Find the bundle that needs to be verified 055 InputStream bundleInputStream = getBundleFileInputStream(bundleName); 056 057 // Find the default bundle which the provided one should be compared to 058 InputStream defaultBundleInputStream = getDefaultBundleFileInputStream(bundleName); 059 060 // and now let's compare! 061 try { 062 // search for missing keys 063 missingKeys = retrieveMissingTranslations(bundleInputStream, defaultBundleInputStream); 064 065 // and now for additional keys 066 bundleInputStream = getBundleFileInputStream(bundleName); 067 defaultBundleInputStream = getDefaultBundleFileInputStream(bundleName); 068 additionalKeys = retrieveMissingTranslations(defaultBundleInputStream, bundleInputStream); 069 070 // And fail only if there are missing keys 071 return missingKeys.isEmpty(); 072 } catch (IOException e) { 073 fail("An error occured while reading the bundles: " + e.getMessage()); 074 return false; 075 } finally { 076 IOUtils.closeQuietly(bundleInputStream); 077 IOUtils.closeQuietly(defaultBundleInputStream); 078 } 079 } 080 081 public void describeTo(Description description) { 082 // report file 083 File dumpFile = new File("target/l10n/" + bundleName + ".report.txt"); 084 085 // prepare message 086 StringBuilder details = prepareDetailsMessage(dumpFile); 087 description.appendText(details.toString()); 088 089 // print report in target directory 090 printReport(dumpFile, details.toString()); 091 } 092 093 private StringBuilder prepareDetailsMessage(File dumpFile) { 094 StringBuilder details = new StringBuilder("\n=======================\n'"); 095 details.append(bundleName); 096 details.append("' is not up-to-date."); 097 print("\n\n Missing translations are:", missingKeys, details); 098 print("\n\nThe following translations do not exist in the reference bundle:", additionalKeys, details); 099 details.append("\n\nSee report file located at: "); 100 details.append(dumpFile.getAbsolutePath()); 101 details.append("\n======================="); 102 return details; 103 } 104 105 private void print(String title, SortedMap<String, String> translations, StringBuilder to) { 106 if (!translations.isEmpty()) { 107 to.append(title); 108 for (Map.Entry<String, String> entry : translations.entrySet()) { 109 to.append("\n").append(entry.getKey()).append("=").append(entry.getValue()); 110 } 111 } 112 } 113 114 private void printReport(File dumpFile, String details) { 115 if (dumpFile.exists()) { 116 dumpFile.delete(); 117 } 118 dumpFile.getParentFile().mkdirs(); 119 FileWriter writer = null; 120 try { 121 writer = new FileWriter(dumpFile); 122 writer.write(details); 123 } catch (IOException e) { 124 throw new IllegalStateException("Unable to write the report to 'target/l10n/" + bundleName + ".report.txt'", e); 125 } finally { 126 IOUtils.closeQuietly(writer); 127 } 128 } 129 130 protected static SortedMap<String, String> retrieveMissingTranslations(InputStream bundle, InputStream referenceBundle) throws IOException { 131 SortedMap<String, String> missingKeys = new TreeMap(); 132 133 Properties bundleProps = loadProperties(bundle); 134 Properties referenceProperties = loadProperties(referenceBundle); 135 136 for (Map.Entry<Object, Object> entry : referenceProperties.entrySet()) { 137 String key = (String) entry.getKey(); 138 if (!bundleProps.containsKey(key)) { 139 missingKeys.put(key, (String) entry.getValue()); 140 } 141 } 142 143 return missingKeys; 144 } 145 146 protected static Properties loadProperties(InputStream inputStream) throws IOException { 147 Properties props = new Properties(); 148 props.load(inputStream); 149 return props; 150 } 151 152 protected static InputStream getBundleFileInputStream(String bundleName) { 153 InputStream bundle = BundleSynchronizedMatcher.class.getResourceAsStream(L10N_PATH + bundleName); 154 assertThat("File '" + bundleName + "' does not exist in '/org/sonar/l10n/'.", bundle, notNullValue()); 155 return bundle; 156 } 157 158 protected static InputStream getDefaultBundleFileInputStream(String bundleName) { 159 String defaultBundleName = extractDefaultBundleName(bundleName); 160 InputStream bundle = BundleSynchronizedMatcher.class.getResourceAsStream(L10N_PATH + defaultBundleName); 161 assertThat("Default bundle '" + defaultBundleName + "' could not be found: add a dependency to the corresponding plugin in your POM.", bundle, notNullValue()); 162 return bundle; 163 } 164 165 protected static String extractDefaultBundleName(String bundleName) { 166 int firstUnderScoreIndex = bundleName.indexOf('_'); 167 assertThat("The bundle '" + bundleName + "' is a default bundle (without locale), so it can't be compared.", firstUnderScoreIndex > 0, 168 is(true)); 169 return bundleName.substring(0, firstUnderScoreIndex) + ".properties"; 170 } 171 172 }