diff options
author | David Gageot <david@gageot.net> | 2012-09-20 17:05:20 +0200 |
---|---|---|
committer | David Gageot <david@gageot.net> | 2012-09-20 17:08:29 +0200 |
commit | bb889f52fb08f10a175ff64b65edd0929a5a69c6 (patch) | |
tree | cbbc50b4af05e3b62c92db250bd694b78e7452d8 /sonar-plugin-api | |
parent | 1b000a65c28f43a3242c55f91a0cc7f6a210e979 (diff) | |
download | sonarqube-bb889f52fb08f10a175ff64b65edd0929a5a69c6.tar.gz sonarqube-bb889f52fb08f10a175ff64b65edd0929a5a69c6.zip |
SONAR-3754 API: ability to define a cardinality on a property
Diffstat (limited to 'sonar-plugin-api')
7 files changed, 255 insertions, 94 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Property.java b/sonar-plugin-api/src/main/java/org/sonar/api/Property.java index 7cff4f9b49f..46f9d0ebe86 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/Property.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/Property.java @@ -92,4 +92,10 @@ public @interface Property { */ String[] options() default {}; + /** + * Can the property take multiple values. Eg: list of email addresses. + * + * @since 3.3 + */ + boolean multiValues() default false; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java index 2e4b646429c..69a8f16c879 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java @@ -66,6 +66,7 @@ public final class PropertyDefinition { private boolean onProject = false; private boolean onModule = false; private boolean isGlobal = true; + private boolean multiValues; private PropertyDefinition(Property annotation) { this.key = annotation.key(); @@ -78,20 +79,20 @@ public final class PropertyDefinition { this.category = annotation.category(); this.type = fixType(annotation.key(), annotation.type()); this.options = annotation.options(); + this.multiValues = annotation.multiValues(); } - private PropertyType fixType(String key, PropertyType type) { + private static PropertyType fixType(String key, PropertyType type) { // Auto-detect passwords and licenses for old versions of plugins that // do not declare property types - PropertyType fix = type; if (type == PropertyType.STRING) { if (StringUtils.endsWith(key, ".password.secured")) { - fix = PropertyType.PASSWORD; + return PropertyType.PASSWORD; } else if (StringUtils.endsWith(key, ".license.secured")) { - fix = PropertyType.LICENSE; + return PropertyType.LICENSE; } } - return fix; + return type; } private PropertyDefinition(String key, PropertyType type, String[] options) { @@ -109,30 +110,28 @@ public final class PropertyDefinition { } public Result validate(@Nullable String value) { - // TODO REFACTORING REQUIRED HERE - Result result = Result.SUCCESS; if (StringUtils.isNotBlank(value)) { if (type == PropertyType.BOOLEAN) { if (!StringUtils.equalsIgnoreCase(value, "true") && !StringUtils.equalsIgnoreCase(value, "false")) { - result = Result.newError("notBoolean"); + return Result.newError("notBoolean"); } } else if (type == PropertyType.INTEGER) { if (!NumberUtils.isDigits(value)) { - result = Result.newError("notInteger"); + return Result.newError("notInteger"); } } else if (type == PropertyType.FLOAT) { try { Double.parseDouble(value); } catch (NumberFormatException e) { - result = Result.newError("notFloat"); + return Result.newError("notFloat"); } } else if (type == PropertyType.SINGLE_SELECT_LIST) { if (!ArrayUtils.contains(options, value)) { - result = Result.newError("notInOptions"); + return Result.newError("notInOptions"); } } } - return result; + return Result.SUCCESS; } public String getKey() { @@ -174,4 +173,11 @@ public final class PropertyDefinition { public boolean isGlobal() { return isGlobal; } + + /** + * @since 3.3 + */ + public boolean isMultiValues() { + return multiValues; + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java index fb541565056..6d7144a65e0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java @@ -19,7 +19,9 @@ */ package org.sonar.api.config; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; import org.apache.commons.lang.StringUtils; import org.sonar.api.BatchComponent; import org.sonar.api.Properties; @@ -97,12 +99,57 @@ public final class PropertyDefinitions implements BatchComponent, ServerComponen return definitions.values(); } + /** + * since 3.3 + */ + public Map<String, Collection<PropertyDefinition>> getGlobalPropertiesByCategory() { + Multimap<String, PropertyDefinition> byCategory = ArrayListMultimap.create(); + + for (PropertyDefinition definition : getAll()) { + if (definition.isGlobal()) { + byCategory.put(getCategory(definition.getKey()), definition); + } + } + + return byCategory.asMap(); + } + + /** + * since 3.3 + */ + public Map<String, Collection<PropertyDefinition>> getProjectPropertiesByCategory() { + Multimap<String, PropertyDefinition> byCategory = ArrayListMultimap.create(); + + for (PropertyDefinition definition : getAll()) { + if (definition.isOnProject()) { + byCategory.put(getCategory(definition.getKey()), definition); + } + } + + return byCategory.asMap(); + } + + /** + * since 3.3 + */ + public Map<String, Collection<PropertyDefinition>> getModulePropertiesByCategory() { + Multimap<String, PropertyDefinition> byCategory = ArrayListMultimap.create(); + + for (PropertyDefinition definition : getAll()) { + if (definition.isOnModule()) { + byCategory.put(getCategory(definition.getKey()), definition); + } + } + + return byCategory.asMap(); + } + public String getDefaultValue(String key) { PropertyDefinition def = get(key); - if (def != null) { - return StringUtils.defaultIfEmpty(def.getDefaultValue(), null); + if (def == null) { + return null; } - return null; + return StringUtils.defaultIfEmpty(def.getDefaultValue(), null); } public String getCategory(String key) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java index 5b118ecd4fc..3ec41a5cf6e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java @@ -19,6 +19,8 @@ */ package org.sonar.api.config; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -29,7 +31,12 @@ import org.sonar.api.ServerComponent; import org.sonar.api.utils.DateUtils; import javax.annotation.Nullable; -import java.util.*; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Properties; /** * Project Settings on batch side, Global Settings on server side. This component does not access to database, so @@ -160,6 +167,20 @@ public class Settings implements BatchComponent, ServerComponent { * </ul> */ public final String[] getStringArray(String key) { + PropertyDefinition property = getDefinitions().get(key); + if ((null != property) && (property.isMultiValues())) { + String value = getString(key); + if (value == null) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + + List<String> values = Lists.newArrayList(); + for (String v : Splitter.on(",").trimResults().split(value)) { + values.add(v.replace("%2C", ",")); + } + return values.toArray(new String[values.size()]); + } + return getStringArrayBySeparator(key, ","); } @@ -213,6 +234,32 @@ public class Settings implements BatchComponent, ServerComponent { return setProperty(key, newValue); } + public final Settings setProperty(String key, @Nullable String[] values) { + PropertyDefinition property = getDefinitions().get(key); + if ((null == property) || (!property.isMultiValues())) { + throw new IllegalStateException("Fail to set multiple values on a single value property " + key); + } + + if (values == null) { + properties.remove(key); + doOnRemoveProperty(key); + } else { + List<String> escaped = Lists.newArrayList(); + for (String value : values) { + if (null != value) { + escaped.add(value.replace(",", "%2C")); + } else { + escaped.add(""); + } + } + + String escapedValue = Joiner.on(',').join(escaped); + properties.put(key, StringUtils.trim(escapedValue)); + doOnSetProperty(key, escapedValue); + } + return this; + } + public final Settings setProperty(String key, @Nullable String value) { if (value == null) { properties.remove(key); diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java index 793b4077356..d65d3af3c86 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java @@ -19,21 +19,15 @@ */ package org.sonar.api.config; -import org.hamcrest.core.Is; import org.junit.Test; import org.sonar.api.Properties; import org.sonar.api.Property; import org.sonar.api.PropertyType; import org.sonar.api.utils.AnnotationUtils; -import java.util.Arrays; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; -import static org.junit.matchers.JUnitMatchers.hasItems; +import static org.fest.assertions.Assertions.assertThat; public class PropertyDefinitionTest { - @Test public void createFromAnnotation() { Properties props = AnnotationUtils.getAnnotation(Init.class, Properties.class); @@ -41,23 +35,22 @@ public class PropertyDefinitionTest { PropertyDefinition def = PropertyDefinition.create(prop); - assertThat(def.getKey(), Is.is("hello")); - assertThat(def.getName(), Is.is("Hello")); - assertThat(def.getDefaultValue(), Is.is("world")); - assertThat(def.getCategory(), Is.is("categ")); - assertThat(def.getOptions().length, Is.is(2)); - assertThat(Arrays.asList(def.getOptions()), hasItems("de", "en")); - assertThat(def.getDescription(), Is.is("desc")); - assertThat(def.getType(), Is.is(PropertyType.FLOAT)); - assertThat(def.isGlobal(), Is.is(false)); - assertThat(def.isOnProject(), Is.is(true)); - assertThat(def.isOnModule(), Is.is(true)); + assertThat(def.getKey()).isEqualTo("hello"); + assertThat(def.getName()).isEqualTo("Hello"); + assertThat(def.getDefaultValue()).isEqualTo("world"); + assertThat(def.getCategory()).isEqualTo("categ"); + assertThat(def.getOptions()).hasSize(2); + assertThat(def.getOptions()).contains("de", "en"); + assertThat(def.getDescription()).isEqualTo("desc"); + assertThat(def.getType()).isEqualTo(PropertyType.FLOAT); + assertThat(def.isGlobal()).isFalse(); + assertThat(def.isOnProject()).isTrue(); + assertThat(def.isOnModule()).isTrue(); + assertThat(def.isMultiValues()).isTrue(); } - @Properties({ - @Property(key = "hello", name = "Hello", defaultValue = "world", description = "desc", - options = {"de", "en"}, category = "categ", type = PropertyType.FLOAT, global = false, project = true, module = true) - }) + @Properties(@Property(key = "hello", name = "Hello", defaultValue = "world", description = "desc", + options = {"de", "en"}, category = "categ", type = PropertyType.FLOAT, global = false, project = true, module = true, multiValues = true)) static class Init { } @@ -68,21 +61,20 @@ public class PropertyDefinitionTest { PropertyDefinition def = PropertyDefinition.create(prop); - assertThat(def.getKey(), Is.is("hello")); - assertThat(def.getName(), Is.is("Hello")); - assertThat(def.getDefaultValue(), Is.is("")); - assertThat(def.getCategory(), Is.is("")); - assertThat(def.getOptions().length, Is.is(0)); - assertThat(def.getDescription(), Is.is("")); - assertThat(def.getType(), Is.is(PropertyType.STRING)); - assertThat(def.isGlobal(), Is.is(true)); - assertThat(def.isOnProject(), Is.is(false)); - assertThat(def.isOnModule(), Is.is(false)); + assertThat(def.getKey()).isEqualTo("hello"); + assertThat(def.getName()).isEqualTo("Hello"); + assertThat(def.getDefaultValue()).isEmpty(); + assertThat(def.getCategory()).isEmpty(); + assertThat(def.getOptions()).isEmpty(); + assertThat(def.getDescription()).isEmpty(); + assertThat(def.getType()).isEqualTo(PropertyType.STRING); + assertThat(def.isGlobal()).isTrue(); + assertThat(def.isOnProject()).isFalse(); + assertThat(def.isOnModule()).isFalse(); + assertThat(def.isMultiValues()).isFalse(); } - @Properties({ - @Property(key = "hello", name = "Hello") - }) + @Properties(@Property(key = "hello", name = "Hello")) static class DefaultValues { } @@ -90,70 +82,68 @@ public class PropertyDefinitionTest { public void validate_string() { PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.STRING, new String[0]); - assertThat(def.validate(null).isValid(), is(true)); - assertThat(def.validate("").isValid(), is(true)); - assertThat(def.validate(" ").isValid(), is(true)); - assertThat(def.validate("foo").isValid(), is(true)); + assertThat(def.validate(null).isValid()).isTrue(); + assertThat(def.validate("").isValid()).isTrue(); + assertThat(def.validate(" ").isValid()).isTrue(); + assertThat(def.validate("foo").isValid()).isTrue(); } @Test public void validate_boolean() { PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.BOOLEAN, new String[0]); - assertThat(def.validate(null).isValid(), is(true)); - assertThat(def.validate("").isValid(), is(true)); - assertThat(def.validate(" ").isValid(), is(true)); - assertThat(def.validate("true").isValid(), is(true)); - assertThat(def.validate("false").isValid(), is(true)); + assertThat(def.validate(null).isValid()).isTrue(); + assertThat(def.validate("").isValid()).isTrue(); + assertThat(def.validate(" ").isValid()).isTrue(); + assertThat(def.validate("true").isValid()).isTrue(); + assertThat(def.validate("false").isValid()).isTrue(); - assertThat(def.validate("foo").isValid(), is(false)); - assertThat(def.validate("foo").getErrorKey(), is("notBoolean")); + assertThat(def.validate("foo").isValid()).isFalse(); + assertThat(def.validate("foo").getErrorKey()).isEqualTo("notBoolean"); } @Test public void validate_integer() { PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.INTEGER, new String[0]); - assertThat(def.validate(null).isValid(), is(true)); - assertThat(def.validate("").isValid(), is(true)); - assertThat(def.validate(" ").isValid(), is(true)); - assertThat(def.validate("123456").isValid(), is(true)); + assertThat(def.validate(null).isValid()).isTrue(); + assertThat(def.validate("").isValid()).isTrue(); + assertThat(def.validate(" ").isValid()).isTrue(); + assertThat(def.validate("123456").isValid()).isTrue(); - assertThat(def.validate("foo").isValid(), is(false)); - assertThat(def.validate("foo").getErrorKey(), is("notInteger")); + assertThat(def.validate("foo").isValid()).isFalse(); + assertThat(def.validate("foo").getErrorKey()).isEqualTo("notInteger"); } @Test public void validate_float() { PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.FLOAT, new String[0]); - assertThat(def.validate(null).isValid(), is(true)); - assertThat(def.validate("").isValid(), is(true)); - assertThat(def.validate(" ").isValid(), is(true)); - assertThat(def.validate("123456").isValid(), is(true)); - assertThat(def.validate("3.14").isValid(), is(true)); + assertThat(def.validate(null).isValid()).isTrue(); + assertThat(def.validate("").isValid()).isTrue(); + assertThat(def.validate(" ").isValid()).isTrue(); + assertThat(def.validate("123456").isValid()).isTrue(); + assertThat(def.validate("3.14").isValid()).isTrue(); - assertThat(def.validate("foo").isValid(), is(false)); - assertThat(def.validate("foo").getErrorKey(), is("notFloat")); + assertThat(def.validate("foo").isValid()).isFalse(); + assertThat(def.validate("foo").getErrorKey()).isEqualTo("notFloat"); } @Test public void validate_single_select_list() { - PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.SINGLE_SELECT_LIST, new String[]{"de", "en"}); + PropertyDefinition def = PropertyDefinition.create("foo", PropertyType.SINGLE_SELECT_LIST, new String[] {"de", "en"}); - assertThat(def.validate(null).isValid(), is(true)); - assertThat(def.validate("").isValid(), is(true)); - assertThat(def.validate(" ").isValid(), is(true)); - assertThat(def.validate("de").isValid(), is(true)); - assertThat(def.validate("en").isValid(), is(true)); + assertThat(def.validate(null).isValid()).isTrue(); + assertThat(def.validate("").isValid()).isTrue(); + assertThat(def.validate(" ").isValid()).isTrue(); + assertThat(def.validate("de").isValid()).isTrue(); + assertThat(def.validate("en").isValid()).isTrue(); - assertThat(def.validate("fr").isValid(), is(false)); - assertThat(def.validate("fr").getErrorKey(), is("notInOptions")); + assertThat(def.validate("fr").isValid()).isFalse(); + assertThat(def.validate("fr").getErrorKey()).isEqualTo("notInOptions"); } - @Properties({ - @Property(key = "scm.password.secured", name = "SCM password") - }) + @Properties(@Property(key = "scm.password.secured", name = "SCM password")) static class OldScmPlugin { } @@ -164,13 +154,11 @@ public class PropertyDefinitionTest { PropertyDefinition def = PropertyDefinition.create(prop); - assertThat(def.getKey(), Is.is("scm.password.secured")); - assertThat(def.getType(), Is.is(PropertyType.PASSWORD)); + assertThat(def.getKey()).isEqualTo("scm.password.secured"); + assertThat(def.getType()).isEqualTo(PropertyType.PASSWORD); } - @Properties({ - @Property(key = "views.license.secured", name = "Views license") - }) + @Properties(@Property(key = "views.license.secured", name = "Views license")) static class ViewsPlugin { } @@ -181,7 +169,7 @@ public class PropertyDefinitionTest { PropertyDefinition def = PropertyDefinition.create(prop); - assertThat(def.getKey(), Is.is("views.license.secured")); - assertThat(def.getType(), Is.is(PropertyType.LICENSE)); + assertThat(def.getKey()).isEqualTo("views.license.secured"); + assertThat(def.getType()).isEqualTo(PropertyType.LICENSE); } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java index 1320d9e376d..9f30157e3bd 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.sonar.api.Properties; import org.sonar.api.Property; +import static org.fest.assertions.Assertions.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; @@ -87,4 +88,23 @@ public class PropertyDefinitionsTest { }) static final class Categories { } + + @Test + public void should_group_by_category() { + PropertyDefinitions def = new PropertyDefinitions(ByCategory.class); + + assertThat(def.getGlobalPropertiesByCategory().keySet()).containsOnly("catGlobal1", "catGlobal2"); + assertThat(def.getProjectPropertiesByCategory().keySet()).containsOnly("catProject"); + assertThat(def.getModulePropertiesByCategory().keySet()).containsOnly("catModule"); + } + + @Properties({ + @Property(key = "global1", name = "Global1", category = "catGlobal1", global = true, project = false, module = false), + @Property(key = "global2", name = "Global2", category = "catGlobal1", global = true, project = false, module = false), + @Property(key = "global3", name = "Global3", category = "catGlobal2", global = true, project = false, module = false), + @Property(key = "project", name = "Project", category = "catProject", global = false, project = true, module = false), + @Property(key = "module", name = "Module", category = "catModule", global = false, project = false, module = true) + }) + static final class ByCategory { + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java index 00757d04646..cef3a3fbdfb 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/config/SettingsTest.java @@ -40,7 +40,8 @@ public class SettingsTest { @Property(key = "boolean", name = "Boolean", defaultValue = "true"), @Property(key = "falseboolean", name = "False Boolean", defaultValue = "false"), @Property(key = "integer", name = "Integer", defaultValue = "12345"), - @Property(key = "array", name = "Array", defaultValue = "one,two,three") + @Property(key = "array", name = "Array", defaultValue = "one,two,three"), + @Property(key = "multi_values", name = "Array", defaultValue = "1,2,3", multiValues = true) }) static class Init { } @@ -154,6 +155,52 @@ public class SettingsTest { } @Test + public void setStringArray() { + Settings settings = new Settings(definitions); + settings.setProperty("multi_values", new String[] {"A", "B"}); + String[] array = settings.getStringArray("multi_values"); + assertThat(array).isEqualTo(new String[] {"A", "B"}); + } + + @Test + public void setStringArrayTrimValues() { + Settings settings = new Settings(definitions); + settings.setProperty("multi_values", new String[] {" A ", " B "}); + String[] array = settings.getStringArray("multi_values"); + assertThat(array).isEqualTo(new String[] {"A", "B"}); + } + + @Test + public void setStringArrayEscapeCommas() { + Settings settings = new Settings(definitions); + settings.setProperty("multi_values", new String[] {"A,B", "C,D"}); + String[] array = settings.getStringArray("multi_values"); + assertThat(array).isEqualTo(new String[] {"A,B", "C,D"}); + } + + @Test + public void setStringArrayWithEmptyValues() { + Settings settings = new Settings(definitions); + settings.setProperty("multi_values", new String[] {"A,B", "", "C,D"}); + String[] array = settings.getStringArray("multi_values"); + assertThat(array).isEqualTo(new String[] {"A,B", "", "C,D"}); + } + + @Test + public void setStringArrayWithNullValues() { + Settings settings = new Settings(definitions); + settings.setProperty("multi_values", new String[] {"A,B", null, "C,D"}); + String[] array = settings.getStringArray("multi_values"); + assertThat(array).isEqualTo(new String[] {"A,B", "", "C,D"}); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailToSetArrayValueOnSingleValueProperty() { + Settings settings = new Settings(definitions); + settings.setProperty("array", new String[] {"A", "B", "C"}); + } + + @Test public void getStringArray_no_value() { Settings settings = new Settings(); String[] array = settings.getStringArray("array"); |