From 75eeadcdaa600a5d2e2729125f7258ebddeb8101 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Thu, 14 Jul 2011 18:02:57 +0200 Subject: [PATCH] SONAR-2591 API : translation mechanism of rule descriptions - Added mechanism to look for rule descriptions in HTML files - Put all the rule names and descriptions of Squid Java in the English Pack using this system --- .../sonar/plugins/core/i18n/I18nManager.java | 162 +++++++++++++----- .../plugins/core/i18n/I18nManagerTest.java | 40 +++++ .../src/test/resources/I18n/EnglishPlugin.jar | Bin 767 -> 1113 bytes .../org/sonar/i18n/test.properties | 1 + .../org/sonar/i18n/test/fakerule.html | 2 + .../src/test/resources/I18n/FrenchPlugin.jar | Bin 1058 -> 1130 bytes .../org/sonar/i18n/test_fr.properties | 2 +- .../org/sonar/i18n/test_fr/fakerule.html | 2 + .../sonar/plugins/i18n/en/EnglishPack.java | 2 +- .../org/sonar/i18n/squidjava.properties | 12 ++ .../squidjava/ArchitecturalConstraint.html | 11 ++ .../squidjava/AvoidBreakOutsideSwitch.html | 18 ++ .../squidjava/AvoidContinueStatement.html | 16 ++ .../squidjava/CallToDeprecatedMethod.html | 2 + .../squidjava/ClassCyclomaticComplexity.html | 14 ++ .../org/sonar/i18n/squidjava/EmptyFile.html | 7 + .../squidjava/MaximumInheritanceDepth.html | 9 + .../squidjava/MethodCyclomaticComplexity.html | 14 ++ .../org/sonar/i18n/squidjava/NoSonar.html | 5 + .../sonar/i18n/squidjava/UndocumentedApi.html | 8 + .../i18n/squidjava/UnusedPrivateMethod.html | 13 ++ .../i18n/squidjava/UnusedProtectedMethod.html | 11 ++ .../org/sonar/i18n/squidjava.properties | 44 ----- 23 files changed, 310 insertions(+), 85 deletions(-) create mode 100644 plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html mode change 100644 => 100755 plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar create mode 100644 plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava.properties create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ArchitecturalConstraint.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidBreakOutsideSwitch.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidContinueStatement.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/CallToDeprecatedMethod.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ClassCyclomaticComplexity.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/EmptyFile.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MaximumInheritanceDepth.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MethodCyclomaticComplexity.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/NoSonar.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UndocumentedApi.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedPrivateMethod.html create mode 100644 plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedProtectedMethod.html delete mode 100644 plugins/sonar-squid-java-plugin/src/main/resources/org/sonar/i18n/squidjava.properties diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java index b8a4c4e5472..66cd05dc970 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java @@ -19,6 +19,7 @@ */ package org.sonar.plugins.core.i18n; +import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; @@ -36,6 +37,7 @@ import java.util.Set; import org.apache.commons.collections.EnumerationUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchExtension; @@ -46,6 +48,7 @@ import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.Logs; import org.sonar.api.utils.SonarException; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -59,6 +62,7 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension private Map keys = Maps.newHashMap(); private Properties unknownKeys = new Properties(); private BundleClassLoader bundleClassLoader = new BundleClassLoader(); + private List registeredLocales = Lists.newArrayList(); public I18nManager(PluginRepository pluginRepository, LanguagePack[] languagePacks) { this.pluginRepository = pluginRepository; @@ -73,13 +77,16 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension doStart(InstalledPlugin.create(pluginRepository)); } - void doStart(List installedPlugins) { + protected void doStart(List installedPlugins) { Logs.INFO.info("Loading i18n bundles"); Set alreadyLoadedResources = Sets.newHashSet(); LanguagePack englishPack = findEnglishPack(); for (InstalledPlugin plugin : installedPlugins) { + // look first in the classloader of the English I18n Plugin of the Sonar platform searchAndStoreBundleNames(plugin.key, englishPack.getClass().getClassLoader(), alreadyLoadedResources); + // then look in the classloader of the plugin + searchAndStoreBundleNames(plugin.key, plugin.classloader, alreadyLoadedResources); } for (LanguagePack pack : languagePacks) { @@ -89,7 +96,7 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension } } - private LanguagePack findEnglishPack() { + protected LanguagePack findEnglishPack() { LanguagePack englishPack = null; for (LanguagePack pack : languagePacks) { if (pack.getLocales().contains(Locale.ENGLISH)) { @@ -103,7 +110,7 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension return englishPack; } - private void addLanguagePack(LanguagePack languagePack) { + protected void addLanguagePack(LanguagePack languagePack) { LOG.debug("Search for bundles in language pack : {}", languagePack); for (String pluginKey : languagePack.getPluginKeys()) { String bundleBaseName = buildBundleBaseName(pluginKey); @@ -113,16 +120,17 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension ClassLoader classloader = languagePack.getClass().getClassLoader(); LOG.info("Adding locale {} for bundleName : {} from classloader : {}", new Object[] { locale, bundleBaseName, classloader }); bundleClassLoader.addResource(bundlePropertiesFile, classloader); + registeredLocales.add(locale); } } } - private String buildBundleBaseName(String pluginKey) { + protected String buildBundleBaseName(String pluginKey) { return packagePathToSearchIn + "/" + pluginKey; } @SuppressWarnings("unchecked") - private void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set alreadyLoadedResources) { + protected void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set alreadyLoadedResources) { String bundleBaseName = buildBundleBaseName(pluginKey); String bundleDefaultPropertiesFile = bundleBaseName + ".properties"; try { @@ -168,61 +176,127 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension } public String message(final Locale locale, final String key, final String defaultText, final Object... objects) { - String result = defaultText; + String translatedMessage = defaultText; try { - String bundleBaseName = keys.get(key); - if (bundleBaseName == null) { - if (result == null) { - throw new MissingResourceException("UNKNOWN KEY : Key '" + key - + "' not found in any bundle, and no default value provided. The key is returned.", bundleBaseName, key); - } - LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText); - unknownKeys.put(key, defaultText); + if (isKeyForRuleDescription(key)) { + // Rule descriptions are in HTML files, not in regular bundles + translatedMessage = findRuleDescription(locale, key, defaultText); } else { - try { - ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader); - - String value = bundle.getString(key); - if ("".equals(value)) { - if (result == null) { - throw new MissingResourceException("VOID KEY : Key '" + key + "' (from bundle '" + bundleBaseName - + "') returns a void value.", bundleBaseName, key); - } - LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[] { key, - bundleBaseName, defaultText }); - } else { - result = value; - } - } catch (MissingResourceException e) { - if (result == null) { - throw e; - } - LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[] { - bundleBaseName, bundleClassLoader, defaultText }); - } + translatedMessage = findStandardMessage(locale, key, defaultText, objects); } } catch (MissingResourceException e) { LOG.warn(e.getMessage()); - if (result == null) { + if (translatedMessage == null) { // when no translation has been found, the key is returned return key; } } catch (Exception e) { LOG.error("Exception when retrieving I18n string: ", e); - if (result == null) { + if (translatedMessage == null) { // when no translation has been found, the key is returned return key; } } + return translatedMessage; + } + + protected boolean isKeyForRuleDescription(String key) { + return StringUtils.startsWith(key, "rule.") && StringUtils.endsWith(key, ".description"); + } + + protected String findRuleDescription(final Locale locale, final String ruleDescriptionKey, final String defaultText) throws IOException { + String translation = defaultText; + String ruleNameKey = ruleDescriptionKey.replace(".description", ".name"); + + String bundleBaseName = keys.get(ruleNameKey); + if (bundleBaseName == null) { + handleMissingBundle(ruleNameKey, defaultText, bundleBaseName); + } else { + Locale localeToUse = defineLocaleToUse(locale); + String htmlFilePath = computeHtmlFilePath(bundleBaseName, ruleDescriptionKey, localeToUse); + ClassLoader classLoader = bundleClassLoader.getClassLoaderForBundle(bundleBaseName, localeToUse); + InputStream stream = classLoader.getResourceAsStream(htmlFilePath); + if (stream == null) { + throw new MissingResourceException("MISSING RULE DESCRIPTION : file '" + htmlFilePath + + "' not found in any bundle. Default value is returned.", bundleBaseName, ruleDescriptionKey); + } + translation = IOUtils.toString(stream, "UTF-8"); + } + + return translation; + } + + protected Locale defineLocaleToUse(final Locale locale) { + Locale localeToUse = locale; + if ( !registeredLocales.contains(locale)) { + localeToUse = Locale.ENGLISH; + } + return localeToUse; + } + + protected String extractRuleName(String ruleDescriptionKey) { + int firstDotIndex = ruleDescriptionKey.indexOf("."); + int secondDotIndex = ruleDescriptionKey.indexOf(".", firstDotIndex + 1); + int thirdDotIndex = ruleDescriptionKey.indexOf(".", secondDotIndex + 1); + return ruleDescriptionKey.substring(secondDotIndex + 1, thirdDotIndex); + } + + protected String computeHtmlFilePath(String bundleBaseName, String ruleDescriptionKey, Locale locale) { + String ruleName = extractRuleName(ruleDescriptionKey); + if (Locale.ENGLISH.equals(locale)) { + return bundleBaseName + "/" + ruleName + ".html"; + } else { + return bundleBaseName + "_" + locale.toString() + "/" + ruleName + ".html"; + } + } + + protected String findStandardMessage(final Locale locale, final String key, final String defaultText, final Object... objects) { + String translation = defaultText; + + String bundleBaseName = keys.get(key); + if (bundleBaseName == null) { + handleMissingBundle(key, defaultText, bundleBaseName); + } else { + try { + ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader); + + String value = bundle.getString(key); + if ("".equals(value)) { + if (translation == null) { + throw new MissingResourceException("VOID KEY : Key '" + key + "' (from bundle '" + bundleBaseName + "') returns a void value.", + bundleBaseName, key); + } + LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[] { key, + bundleBaseName, defaultText }); + } else { + translation = value; + } + } catch (MissingResourceException e) { + if (translation == null) { + throw e; + } + LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[] { + bundleBaseName, bundleClassLoader, defaultText }); + } + } if (objects.length > 0) { LOG.debug("Translation : {}, {}, {}, {}", new String[] { locale.toString(), key, defaultText, Arrays.deepToString(objects) }); - return MessageFormat.format(result, objects); + return MessageFormat.format(translation, objects); } else { - return result; + return translation; } } + protected void handleMissingBundle(final String key, final String defaultText, String bundleBaseName) { + if (defaultText == null) { + throw new MissingResourceException("UNKNOWN KEY : Key '" + key + + "' not found in any bundle, and no default value provided. The key is returned.", bundleBaseName, key); + } + LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText); + unknownKeys.put(key, defaultText); + } + /** * @return the unknownKeys */ @@ -242,6 +316,16 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension resources.put(resourceName, classloader); } + public ClassLoader getClassLoaderForBundle(String bundleBaseName, Locale locale) { + StringBuilder resourceName = new StringBuilder(bundleBaseName); + if (locale != null && !locale.equals(Locale.ENGLISH)) { + resourceName.append("_"); + resourceName.append(locale); + } + resourceName.append(".properties"); + return resources.get(resourceName.toString()); + } + @Override public URL findResource(String name) { if (resources.containsKey(name)) { diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java index f8b8a299975..bf66390c9e6 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java @@ -19,7 +19,11 @@ */ package org.sonar.plugins.core.i18n; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import java.net.URL; @@ -106,6 +110,42 @@ public class I18nManagerTest { Assert.assertEquals(0, manager.getUnknownKeys().size()); } + @Test + public void testIsKeyForRuleDescription() throws Exception { + assertTrue(manager.isKeyForRuleDescription("rule.squid.ArchitecturalConstraint.description")); + assertFalse(manager.isKeyForRuleDescription("rule.squid.ArchitecturalConstraint.name")); + assertFalse(manager.isKeyForRuleDescription("another.key")); + } + + @Test + public void testDefineLocaleToUse() throws Exception { + assertThat(manager.defineLocaleToUse(Locale.CANADA_FRENCH), is(Locale.CANADA_FRENCH)); + // Locale not supported => get the English one + assertThat(manager.defineLocaleToUse(Locale.JAPAN), is(Locale.ENGLISH)); + } + + @Test + public void testExtractRuleName() throws Exception { + assertThat(manager.extractRuleName("rule.squid.ArchitecturalConstraint.description"), is("ArchitecturalConstraint")); + } + + @Test + public void testComputeHtmlFilePath() throws Exception { + assertThat(manager.computeHtmlFilePath("org/sonar/i18n/test", "rule.test.fakerule.description", Locale.FRENCH), + is("org/sonar/i18n/test_fr/fakerule.html")); + assertThat(manager.computeHtmlFilePath("org/sonar/i18n/test", "rule.test.fakerule.description", Locale.ENGLISH), + is("org/sonar/i18n/test/fakerule.html")); + } + + @Test + public void shouldReturnRuleDescriptionFromHTMLFile() throws Exception { + String result = manager.message(Locale.FRENCH, "rule.test.fakerule.description", "foo"); + assertThat(result, is("

Règle bidon

\nC'est la description de la règle bidon.")); + // Locale not supported => get the English translation + result = manager.message(Locale.JAPAN, "rule.test.fakerule.description", "foo"); + assertThat(result, is("

Fake Rule

\nThis is the description of the fake rule.")); + } + public static class TestClassLoader extends URLClassLoader { public TestClassLoader(URL url) { diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar index a03984571794e136a6a6e8399c63101f30552db4..14d66ce530db105466df99c95563913d6feafd83 100755 GIT binary patch literal 1113 zcmWIWW@Zs#0D)T*-`as0P=XUk`?`iW>U#RQ=?6fSae$R=Uw)~>5h!f~#30RZWxkGn zo^GzeA$q=Uj5Z91|NAE9Wu~PTm*|G278Pga=UFKj>KX8IITxiSmZYZWI#q%M%=8T6 z4b2ra9199^QWZS&lJzusxp=u?R${u01!!e{Q99gFm@2rSDvI;-5{r=3&5e79;zxcI zb(w}1c}R+vqbUaY6l5*LP_W_>px@v+I2g79b$oiJ*XICK2?`rU96Hhxvr~&ob5iv( zN^)~#yAE<5P~c(s?>CWC7%p2 z$7x5vsR!jn`4?W#e^t)E_bnSNoH!U}0IfH-jyvlCwAKfR<*-|?S5TB+kXlrdnOZD+ z+>o!qfP>+{-0&YvbqBJN?)-5Kz9=+NYGL#K?Ym-%giKgFCR;pNrCsY_T00>({G*ex zyWZQT-I~c8*z;aKsGsJyY-4^zReXRqBa;X-?sNon2oMM`yalBiL~=oB!=A1{ia}sW zqdJhpmdX$&U?u`kGJyb)#j}7+NTR{*R8X>k0FaJ7Kqex-k!`}3j1c;spy-1pDukuj zk`+R;AS1G+$cYSQ0`_DEF#!}*&Nxg!NpuL`U{5v(;|^gr4k-}@c(byB)N=sgU!ZC? HCJ+w*0(J@w delta 234 zcmcb~@t>6^z?+#xgaHH!dMEOzGAAcpn`lzNl+inJnF5GBp~k$q_wB@w8p0e5KsmeH zwFake Rule +This is the description of the fake rule. \ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar old mode 100644 new mode 100755 index ab61bd88697b95679b6be178af00fc16f16b0949..383c767d26d2adbdb12aae9a427e68d45dc41041 GIT binary patch literal 1130 zcmWIWW@Zs#0D)T*-`as0P=XUk`?`iW>U#RQ=?6fSae$R=Uw)~>5h!f~#30RZWxkGn zo^GzeA$q=Uj5Z91|NAE9Wu~PTm*|G278Pga=UFKj>KX8IITxiSmZYZWI#q%M%=8T6 z4b2ra9199^QWZS&lJzusxp=u?R${u01!!e{Q99gFm@2rSDvI;-5{r=3y^4E>;zxcI zb(w}1c}R+%qbU~0R9ph|TYMVA1`dYrKuyz<`1zfIia}PX;MSCun4MZwnv<%RQIebE zduk)+0R;hvi*~^=)Af?{GdDF#OW2-iywhnF@~yyd>*n7!`+k1O{Vgwi$lLM83eMxj zNx2hbSk~+d$WyVY_PqHln*kPJ91M?vj=0=ca^D4Lxf2j8;&g;wK~a7|YEemMYO(Bb zL%s$B4u%7B!+$g_Tg=U0P+I25=zL_-mM{O+?v+j3VR^$r)qBSw$*t{%UtR_Uxrr@x z|7Tjo`aSuY*6X*M<{Z2DfH}aMkx7IZcX|T40|*2d-hxsOBH19cVNYKm#UQYxQ5{HP zOKk`fFcSkPxj+EO;#oi@B=O*ODk%9t07%CkAQPkmmY5Lwuq7viz9%U9pot5i8C&u~ zXclBdwiK)xk=S5{U{7ukLqMVBhT9O7#D@qy>}dyK;%S^FBBi7NZ&o&t77id}V`5R zoUk=E+m~PJumOe!2#X+xhOeWar<-eVh@P(-IAAhzUU`Esn%8_2^D@&?i%WFFQj3Z+ z^Yg3}4D}3nxtxnq6H8K4be$?e0%m#!@rLFK8jb}8IjIVsdC7X3yj;9oh(MJBdTA5O zw?rwRD?nHg(@P~lOXAat^a_gd3sQ?pGE+C2Ee8PshPRF&8YywGLJ|j>|M3~e!hmd?Gtf9lLLuEG zE)#85_{aZ4KcfD}CRP!bc+WGo2@ uIUq5U1G338fF^?i65@TZbFn23pf#Yxf#CsGHlX_$*nsdq&>&tW5Dx&ym*4XM diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties index cc8efa7afc3..a1d8766e962 100644 --- a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties +++ b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties @@ -1,4 +1,4 @@ it=Il is=fait cold=froid - +rule.test.fakerule.name=R\u00e8gle bidon diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html new file mode 100644 index 00000000000..94c0d0869f8 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html @@ -0,0 +1,2 @@ +

Règle bidon

+C'est la description de la règle bidon. \ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java b/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java index de0c47bc0e1..62213736882 100644 --- a/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java +++ b/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java @@ -28,7 +28,7 @@ import java.util.Locale; public class EnglishPack extends LanguagePack { public List getPluginKeys() { - return Arrays.asList("core", "design"); + return Arrays.asList("core", "design", "squidjava"); } public List getLocales() { diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava.properties b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava.properties new file mode 100644 index 00000000000..1470cf5dfd2 --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava.properties @@ -0,0 +1,12 @@ +rule.squid.ArchitecturalConstraint.name=Architectural constraint +rule.squid.AvoidBreakOutsideSwitch.name=Avoid using 'break' branching statement outside a 'switch' statement +rule.squid.AvoidContinueStatement.name=Avoid using 'continue' branching statement +rule.squid.CallToDeprecatedMethod.name=Avoid use of deprecated method +rule.squid.ClassCyclomaticComplexity.name=Avoid too complex class +rule.squid.EmptyFile.name=Empty file +rule.squid.MaximumInheritanceDepth.name=Avoid too deep inheritance tree +rule.squid.MethodCyclomaticComplexity.name=Avoid too complex method +rule.squid.NoSonar.name=Avoid use of //NOSONAR marker +rule.squid.UndocumentedApi.name=Undocumented API +rule.squid.UnusedPrivateMethod.name=Unused private method +rule.squid.UnusedProtectedMethod.name=Unused protected method diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ArchitecturalConstraint.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ArchitecturalConstraint.html new file mode 100644 index 00000000000..2af771f2944 --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ArchitecturalConstraint.html @@ -0,0 +1,11 @@ +

A source code comply to an architectural model when it fully + adheres to a set of architectural constraints. A constraint allows to + deny references between classes by pattern.

+

You can for instance use this rule to :

+
    +
  • forbid access to **.web.** from **.dao.** classes
  • +
  • forbid access to java.util.Vector, java.util.Hashtable and + java.util.Enumeration from any classes
  • +
  • forbid access to java.sql.** from **.ui.** and **.web.** + classes
  • +
\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidBreakOutsideSwitch.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidBreakOutsideSwitch.html new file mode 100644 index 00000000000..d691586270a --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidBreakOutsideSwitch.html @@ -0,0 +1,18 @@ +

The use of the 'break' branching statement increases the + essential complexity of the source code and so prevents any refactoring + of this source code to replace all well structured control structures + with a single statement.

+

For instance, with the following java program fragment, it's not + possible to apply the 'extract method' refactoring pattern :

+
+mylabel : for (int i = 0 ; i< 3; i++) {
+  for (int j = 0; j < 4 ; j++) {
+    doSomething();
+    if (checkSomething()) {
+      break mylabel;
+    }
+  }
+}
+
+

The use of the 'break' branching statement is only authorized + inside a 'switch' statement.

\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidContinueStatement.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidContinueStatement.html new file mode 100644 index 00000000000..d52b67eba23 --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/AvoidContinueStatement.html @@ -0,0 +1,16 @@ +

The use of the 'continue' branching statement increase the + essential complexity of the source code and so prevent any refactoring + of this source code to replace all well structured control structures + with a single statement.

+

For instance, in the following java program fragment, it's not + possible to apply the 'extract method' refactoring pattern :

+
+mylabel : for(int i = 0 ; i< 3; i++) {
+  for (int j = 0; j < 4 ; j++) {
+    doSomething();
+    if (checkSomething()) {
+      continue mylabel;
+    }
+  }
+}
+
\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/CallToDeprecatedMethod.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/CallToDeprecatedMethod.html new file mode 100644 index 00000000000..146b8f3b13d --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/CallToDeprecatedMethod.html @@ -0,0 +1,2 @@ +

Once deprecated, a method should no longer be used as it means + that the method might be removed sooner or later.

\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ClassCyclomaticComplexity.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ClassCyclomaticComplexity.html new file mode 100644 index 00000000000..a2837927eeb --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/ClassCyclomaticComplexity.html @@ -0,0 +1,14 @@ +

The Cyclomatic Complexity is measured by the number of (&&, ||) + operators and (if, while, do, for, ?:, catch, switch, case, return, + throw) statements in the body of a class plus one for each constructor, + method (but not getter/setter), static initializer, or instance + initializer in the class. The last return stament in method, if exists, + is not taken into account.

+

+ Even when the Cyclomatic Complexity of a class is very high, this + complexity might be well distributed among all methods. Nevertheless, + most of the time, a very complex class is a class which breaks the Single + Responsibility Principle and which should be re-factored to be split + in several classes. +

\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/EmptyFile.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/EmptyFile.html new file mode 100644 index 00000000000..9b3a8772360 --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/EmptyFile.html @@ -0,0 +1,7 @@ +

Detect empty files, which do not have any lines of code.

+

Example:

+
+//package org.foo;
+//
+//public class Bar {}
+
\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MaximumInheritanceDepth.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MaximumInheritanceDepth.html new file mode 100644 index 00000000000..61fc1884394 --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MaximumInheritanceDepth.html @@ -0,0 +1,9 @@ +

Inheritance is certainly one of the most valuable concept of + object-oriented programming. It's a way to compartmentalize and reuse + code by creating collections of attributes and behaviors called classes + which can be based on previously created classes. But abusing of this + concept by creating a deep inheritance tree can lead to very complex + and unmaintainable source code.

+

Most of the time a too deep inheritance tree is due to bad object + oriented design which has led to systematically use 'inheritance' when + 'composition' would suit better.

\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MethodCyclomaticComplexity.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MethodCyclomaticComplexity.html new file mode 100644 index 00000000000..d50d8825d7a --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/MethodCyclomaticComplexity.html @@ -0,0 +1,14 @@ +

The Cyclomatic Complexity is measured by the number of + (&&, ||) operators and (if, while, do, for, ?:, catch, switch, + case, return, throw) statements in the body of a class plus one for + each constructor, method (but not getter/setter), static initializer, + or instance initializer in the class. The last return stament in + method, if exists, is not taken into account.

+

+ Even when the Cyclomatic Complexity of a class is very high, this + complexity might be well distributed among all methods. Nevertheless, + most of the time, a very complex class is a class which breaks the Single + Responsibility Principle and which should be re-factored to be split + in several classes. +

\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/NoSonar.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/NoSonar.html new file mode 100644 index 00000000000..c56106d83c1 --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/NoSonar.html @@ -0,0 +1,5 @@ +

Any violation to quality rule can be deactivated with the + //NOSONAR marker. This marker is pretty useful to exclude + false-positive results but sometimes it can abusively be used to hide + real quality flaws.

+

This rule allows to track and/or forbid use of this marker

\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UndocumentedApi.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UndocumentedApi.html new file mode 100644 index 00000000000..9745f9eab3d --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UndocumentedApi.html @@ -0,0 +1,8 @@ +

Check that each public class, interface, method and constructor + has a Javadoc comment. The following public methods/constructors are + not concerned by this rule :

+
    +
  • Getter / Setter
  • +
  • Method with @Override annotation
  • +
  • Empty constructor
  • +
\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedPrivateMethod.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedPrivateMethod.html new file mode 100644 index 00000000000..fd3901a49fa --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedPrivateMethod.html @@ -0,0 +1,13 @@ +

Private methods that are never executed are dead code. Dead code + means unnecessary, inoperative code that should be removed. This helps + in maintenance by decreasing the maintained code size, making it easier + to understand the program and preventing bugs from being introduced.

+

In the following two cases, private methods are not considered as + dead code by Sonar :

+
    +
  • Private empty constructors that are intentionally used to + prevent any direct instantiation of a class.
  • +
  • Private methods : readObject(...), writeObject(...), + writeReplace(...), readResolve(...) which can contractually be used + when implementing the Serializable interface.
  • +
\ No newline at end of file diff --git a/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedProtectedMethod.html b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedProtectedMethod.html new file mode 100644 index 00000000000..ec8ed1032fd --- /dev/null +++ b/plugins/sonar-i18n-en-plugin/src/main/resources/org/sonar/i18n/squidjava/UnusedProtectedMethod.html @@ -0,0 +1,11 @@ +

Protected methods that are never used by any classes in the same + project are strongly suspected to be dead code. Dead code means + unnecessary, inoperative code that should be removed. This helps in + maintenance by decreasing the maintained code size, making it easier to + understand the program and preventing bugs from being introduced.

+

In the following case, unused protected methods are not + considered as dead code by Sonar :

+
    +
  • Protected methods which override a method from a parent class.
  • +
  • Protected methods of an abstract class.
  • +
\ No newline at end of file diff --git a/plugins/sonar-squid-java-plugin/src/main/resources/org/sonar/i18n/squidjava.properties b/plugins/sonar-squid-java-plugin/src/main/resources/org/sonar/i18n/squidjava.properties deleted file mode 100644 index 65fd6542851..00000000000 --- a/plugins/sonar-squid-java-plugin/src/main/resources/org/sonar/i18n/squidjava.properties +++ /dev/null @@ -1,44 +0,0 @@ -rule.squid.ArchitecturalConstraint.name=Architectural constraint -rule.squid.ArchitecturalConstraint.description=

A source code comply to an architectural model when it fully adheres to a set of architectural constraints. A constraint allows to deny references between classes by pattern.

You can for instance use this rule to :

  • forbid access to **.web.** from **.dao.** classes
  • forbid access to java.util.Vector, java.util.Hashtable and java.util.Enumeration from any classes
  • forbid access to java.sql.** from **.ui.** and **.web.** classes
-rule.squid.AvoidBreakOutsideSwitch.name=Avoid using 'break' branching statement outside a 'switch' statement -rule.squid.AvoidBreakOutsideSwitch.description=

The use of the 'break' branching statement increases the essential complexity of the source code and so prevents any refactoring of this source code to replace all well structured control structures with a single statement.

For instance, with the following java program fragment, it's not possible to apply the 'extract method' refactoring pattern :

mylabel : for (int i = 0 ; i< 3; i++) {\
-  for (int j = 0; j < 4 ; j++) {\
-    doSomething();\
-    if (checkSomething()) {\
-      break mylabel;\
-    }\
-  }\
-}\
-

The use of the 'break' branching statement is only authorized inside a 'switch' statement.

-rule.squid.AvoidContinueStatement.name=Avoid using 'continue' branching statement -rule.squid.AvoidContinueStatement.description=

The use of the 'continue' branching statement increase the essential complexity of the source code and so prevent any refactoring of this source code to replace all well structured control structures with a single statement.

For instance, in the following java program fragment, it's not possible to apply the 'extract method' refactoring pattern :

mylabel : for(int i = 0 ; i< 3; i++) {\
-  for (int j = 0; j < 4 ; j++) {\
-    doSomething();\
-    if (checkSomething()) {\
-      continue mylabel;\
-    }\
-  }\
-}\
-
-rule.squid.CallToDeprecatedMethod.name=Avoid use of deprecated method -rule.squid.CallToDeprecatedMethod.description=

Once deprecated, a method should no longer be used as it means that the method might be removed sooner or later.

-rule.squid.ClassCyclomaticComplexity.name=Avoid too complex class -rule.squid.ClassCyclomaticComplexity.description=

The Cyclomatic Complexity is measured by the number of (&&, ||) operators and (if, while, do, for, ?:, catch, switch, case, return, throw) statements in the body of a class plus one for each constructor, method (but not getter/setter), static initializer, or instance initializer in the class. The last return stament in method, if exists, is not taken into account.

Even when the Cyclomatic Complexity of a class is very high, this complexity might be well distributed among all methods. Nevertheless, most of the time, a very complex class is a class which breaks the Single Responsibility Principle and which should be re-factored to be split in several classes.

-rule.squid.EmptyFile.name=Empty file -rule.squid.EmptyFile.description=Detect empty files, which do not have any lines of code. Example:
\
-//package org.foo;\
-//\
-//public class Bar {}\
-
-rule.squid.MaximumInheritanceDepth.name=Avoid too deep inheritance tree -rule.squid.MaximumInheritanceDepth.description=

Inheritance is certainly one of the most valuable concept of object-oriented programming. It's a way to compartmentalize and reuse code by creating collections of attributes and behaviors called classes which can be based on previously created classes. But abusing of this concept by creating a deep inheritance tree can lead to very complex and unmaintainable source code.

Most of the time a too deep inheritance tree is due to bad object oriented design which has led to systematically use 'inheritance' when 'composition' would suit better.

-rule.squid.MethodCyclomaticComplexity.name=Avoid too complex method -rule.squid.MethodCyclomaticComplexity.description=

The Cyclomatic Complexity is measured by the number of (&&, ||) operators and (if, while, do, for, ?:, catch, switch, case, return, throw) statements in the body of a constructor, method, static initializer, or instance initializer. The minimun Cyclomatic Complexity of a method is 1 and the last return stament, if exists, is not taken into account. The more complex is a method, the more possible different paths through the source code exist. Generally 1-4 is considered good, 5-7 ok, 8-10 consider re-factoring, and 11+ re-factor now. Indeed above 10, it's pretty difficult to be able to think about all possible paths when maintaining the source code, so the risk of regression increases exponentially.

-rule.squid.NoSonar.name=Avoid use of //NOSONAR marker -rule.squid.NoSonar.description=

Any violation to quality rule can be deactivated with the //NOSONAR marker. This marker is pretty useful to exclude false-positive results but sometimes it can abusively be used to hide real quality flaws.

This rule allows to track and/or forbid use of this marker

-rule.squid.UndocumentedApi.name=Undocumented API -rule.squid.UndocumentedApi.description=

Check that each public class, interface, method and constructor has a Javadoc comment. The following public methods/constructors are not concerned by this rule :

  • Getter / Setter
  • Method with @Override annotation
  • Empty constructor
-rule.squid.UnusedPrivateMethod.name=Unused private method -rule.squid.UnusedPrivateMethod.description=

Private methods that are never executed are dead code. Dead code means unnecessary, inoperative code that should be removed. This helps in maintenance by decreasing the maintained code size, making it easier to understand the program and preventing bugs from being introduced.

In the following two cases, private methods are not considered as dead code by Sonar :

  • Private empty constructors that are intentionally used to prevent any direct instantiation of a class.
  • Private methods : readObject(...), writeObject(...), writeReplace(...), readResolve(...) which can contractually be used when implementing the Serializable interface.
-rule.squid.UnusedProtectedMethod.name=Unused protected method -rule.squid.UnusedProtectedMethod.description=

Protected methods that are never used by any classes in the same project are strongly suspected to be dead code. Dead code means unnecessary, inoperative code that should be removed. This helps in maintenance by decreasing the maintained code size, making it easier to understand the program and preventing bugs from being introduced.

In the following case, unused protected methods are not considered as dead code by Sonar :

  • Protected methods which override a method from a parent class.
  • Protected methods of an abstract class.
-- 2.39.5