--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.technicaldebt;
+
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.CharacteristicProperty;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.util.List;
+
+public class TechnicalDebtMergeModel {
+
+ private Model model;
+
+ private List<Characteristic> defaultCharacteristics;
+
+ public TechnicalDebtMergeModel(Model model, List<Characteristic> defaultCharacteristics) {
+ this.model = model;
+ this.defaultCharacteristics = defaultCharacteristics;
+ }
+
+ public void mergeWith(Model with, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
+ for (Characteristic characteristic : with.getCharacteristics()) {
+ if (isRequirement(characteristic)) {
+ mergeRequirement(characteristic, messages, technicalDebtRuleCache);
+ } else {
+ mergeCharacteristic(characteristic, messages);
+ }
+ }
+ }
+
+ private Characteristic mergeCharacteristic(Characteristic characteristic, ValidationMessages messages) {
+ Characteristic existingCharacteristic = model.getCharacteristicByKey(characteristic.getKey());
+ if (existingCharacteristic == null) {
+ if (defaultCharacteristics.contains(characteristic)) {
+ existingCharacteristic = model.addCharacteristic(clone(characteristic));
+ if (!characteristic.getParents().isEmpty()) {
+ Characteristic parentTargetCharacteristic = mergeCharacteristic(characteristic.getParents().get(0), messages);
+ parentTargetCharacteristic.addChild(existingCharacteristic);
+ }
+ } else {
+ throw new IllegalArgumentException("The characteristic : " + characteristic.getKey() + " cannot be used as it's not available in default ones.");
+ }
+ }
+ return existingCharacteristic;
+ }
+
+ private void mergeRequirement(Characteristic requirement, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
+ Characteristic targetRequirement = model.getCharacteristicByRule(requirement.getRule());
+ if (targetRequirement == null && !requirement.getParents().isEmpty()) {
+ Rule rule = technicalDebtRuleCache.getRule(requirement.getRule().getRepositoryKey(), requirement.getRule().getKey());
+ if (rule == null) {
+ messages.addWarningText("The rule " + requirement.getRule() + " does not exist.");
+
+ } else {
+ Characteristic parent = mergeCharacteristic(requirement.getParents().get(0), messages);
+ requirement = model.addCharacteristic(clone(requirement));
+ requirement.setRule(rule);
+ parent.addChild(requirement);
+ }
+ }
+ }
+
+ private boolean isRequirement(Characteristic characteristic) {
+ return characteristic.hasRule();
+ }
+
+ private Characteristic clone(Characteristic c) {
+ Characteristic clone = Characteristic.create();
+ clone.setRule(c.getRule());
+ clone.setDescription(c.getDescription());
+ clone.setKey(c.getKey());
+ clone.setName(c.getName(), false);
+ for (CharacteristicProperty property : c.getProperties()) {
+ clone.setProperty(property.getKey(), property.getTextValue()).setValue(property.getValue());
+ }
+ return clone;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.technicaldebt;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+import org.picocontainer.Startable;
+import org.sonar.api.ServerExtension;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ * <p>This class is used to find which technical debt model XML files exist in the Sonar instance.</p>
+ * <p>
+ * Those XML files are provided by language plugins that embed their own contribution to the definition of the Technical debt model.
+ * They must be located in the classpath of those language plugins, more specifically in the "com.sonar.sqale" package, and
+ * they must be named "<pluginKey>-model.xml".
+ * </p>
+ */
+public class TechnicalDebtModelFinder implements ServerExtension, Startable {
+
+ public static final String DEFAULT_MODEL = "technical-debt";
+
+ private static final String XML_FILE_SUFFIX = "-model.xml";
+ private static final String XML_FILE_PREFIX = "com/sonar/sqale/";
+
+ private String xmlFilePrefix;
+
+ private PluginRepository pluginRepository;
+ private Map<String, ClassLoader> contributingPluginKeyToClassLoader;
+
+ public TechnicalDebtModelFinder(PluginRepository pluginRepository) {
+ this.pluginRepository = pluginRepository;
+ this.xmlFilePrefix = XML_FILE_PREFIX;
+ }
+
+ @VisibleForTesting
+ TechnicalDebtModelFinder(PluginRepository pluginRepository, String xmlFilePrefix) {
+ this.pluginRepository = pluginRepository;
+ this.xmlFilePrefix = xmlFilePrefix;
+ }
+
+ @VisibleForTesting
+ TechnicalDebtModelFinder(Map<String, ClassLoader> contributingPluginKeyToClassLoader, String xmlFilePrefix) {
+ this.contributingPluginKeyToClassLoader = contributingPluginKeyToClassLoader;
+ this.xmlFilePrefix = xmlFilePrefix;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void start() {
+ findAvailableXMLFiles();
+ }
+
+ protected void findAvailableXMLFiles() {
+ if (contributingPluginKeyToClassLoader == null) {
+ contributingPluginKeyToClassLoader = Maps.newTreeMap();
+ for (PluginMetadata metadata : pluginRepository.getMetadata()) {
+ String pluginKey = metadata.getKey();
+ ClassLoader classLoader = pluginRepository.getPlugin(pluginKey).getClass().getClassLoader();
+ if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
+ contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
+ }
+ }
+ // Add default model
+ contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
+ }
+ contributingPluginKeyToClassLoader = Collections.unmodifiableMap(contributingPluginKeyToClassLoader);
+ }
+
+ @VisibleForTesting
+ String getXMLFilePath(String model) {
+ return xmlFilePrefix + model + XML_FILE_SUFFIX;
+ }
+
+ /**
+ * Returns the list of plugins that can contribute to the technical debt model (without the default model).
+ *
+ * @return the list of plugin keys
+ */
+ public Collection<String> getContributingPluginList() {
+ Collection<String> contributingPlugins = newArrayList(contributingPluginKeyToClassLoader.keySet());
+ contributingPlugins.remove(DEFAULT_MODEL);
+ return contributingPlugins;
+ }
+
+ /**
+ * Creates a new {@link java.io.Reader} for the XML file that contains the model contributed by the given plugin.
+ *
+ * @param pluginKey the key of the plugin that contributes the XML file
+ * @return the reader, that must be closed once its use is finished.
+ */
+ public Reader createReaderForXMLFile(String pluginKey) {
+ ClassLoader classLoader = contributingPluginKeyToClassLoader.get(pluginKey);
+ String xmlFilePath = getXMLFilePath(pluginKey);
+ return new InputStreamReader(classLoader.getResourceAsStream(xmlFilePath));
+ }
+
+ @VisibleForTesting
+ Map<String, ClassLoader> getContributingPluginKeyToClassLoader(){
+ return contributingPluginKeyToClassLoader;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void stop() {
+ // Nothing to do
+ }
+
+}
return offset;
}
- public org.sonar.api.qualitymodel.Characteristic toCharacteristic() {
+ public Characteristic toCharacteristic() {
return characteristic;
}
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.technicaldebt;
+
+import com.google.common.collect.Maps;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TechnicalDebtRuleCache {
+
+ private final RuleFinder ruleFinder;
+
+ private Map<String, Map<String, Rule>> cachedRules;
+
+ public TechnicalDebtRuleCache(RuleFinder ruleFinder) {
+ this.ruleFinder = ruleFinder;
+ }
+
+ @CheckForNull
+ public Rule getRule(String repository, String ruleKey) {
+ initRules();
+ return lookUpRuleInCache(repository, ruleKey);
+ }
+
+ public boolean exists(Rule rule) {
+ return getRule(rule.getRepositoryKey(), rule.getKey()) != null;
+ }
+
+ private void initRules(){
+ if(cachedRules == null) {
+ loadRules();
+ }
+ }
+
+ private void loadRules() {
+ cachedRules = Maps.newHashMap();
+ Collection<Rule> rules = ruleFinder.findAll(RuleQuery.create());
+ for (Rule rule : rules) {
+ if(!cachedRules.containsKey(rule.getRepositoryKey())) {
+ cachedRules.put(rule.getRepositoryKey(), new HashMap<String, Rule>());
+ }
+ Map<String, Rule> cachedRepository = cachedRules.get(rule.getRepositoryKey());
+ if(!cachedRepository.containsKey(rule.getKey())) {
+ cachedRepository.put(rule.getKey(), rule);
+ }
+ }
+ }
+
+ @CheckForNull
+ private Rule lookUpRuleInCache(String repository, String ruleKey) {
+ Map<String, Rule> cachedRepository = cachedRules.get(repository);
+ if(cachedRepository != null) {
+ return cachedRepository.get(ruleKey);
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.technicaldebt;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.NumberUtils;
+import org.codehaus.stax2.XMLInputFactory2;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.ServerExtension;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.utils.ValidationMessages;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+
+public class TechnicalDebtXMLImporter implements ServerExtension {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TechnicalDebtXMLImporter.class);
+
+ private static final String CHARACTERISTIC = "chc";
+ private static final String CHARACTERISTIC_KEY = "key";
+ private static final String CHARACTERISTIC_NAME = "name";
+ private static final String CHARACTERISTIC_DESCRIPTION = "desc";
+ private static final String PROPERTY = "prop";
+ private static final String PROPERTY_KEY = "key";
+ private static final String PROPERTY_VALUE = "val";
+ private static final String PROPERTY_TEXT_VALUE = "txt";
+
+ public Model importXML(String xml, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) {
+ return importXML(new StringReader(xml), messages, technicalDebtRuleCache);
+ }
+
+ public Model importXML(Reader xml, ValidationMessages messages, TechnicalDebtRuleCache repositoryCache) {
+ Model model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+ try {
+ SMInputFactory inputFactory = initStax();
+ SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);
+
+ // advance to <sqale>
+ cursor.advance();
+ SMInputCursor chcCursor = cursor.childElementCursor(CHARACTERISTIC);
+
+ while (chcCursor.getNext() != null) {
+ processCharacteristic(model, chcCursor, messages, repositoryCache);
+ }
+
+ cursor.getStreamReader().closeCompletely();
+
+ } catch (XMLStreamException e) {
+ LOG.error("XML is not valid", e);
+ messages.addErrorText("XML is not valid: " + e.getMessage());
+ }
+ return model;
+ }
+
+ private SMInputFactory initStax() {
+ XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
+ xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+ xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+ xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+ xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
+ return new SMInputFactory(xmlFactory);
+ }
+
+ private Characteristic processCharacteristic(Model model, SMInputCursor chcCursor, ValidationMessages messages, TechnicalDebtRuleCache technicalDebtRuleCache) throws XMLStreamException {
+ Characteristic characteristic = Characteristic.create();
+ SMInputCursor cursor = chcCursor.childElementCursor();
+
+ String ruleRepositoryKey = null, ruleKey = null;
+ List<Characteristic> children = Lists.newArrayList();
+ while (cursor.getNext() != null) {
+ String node = cursor.getLocalName();
+ if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
+ characteristic.setKey(cursor.collectDescendantText().trim());
+
+ } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
+ characteristic.setName(cursor.collectDescendantText().trim(), false);
+
+ } else if (StringUtils.equals(node, CHARACTERISTIC_DESCRIPTION)) {
+ characteristic.setDescription(cursor.collectDescendantText().trim());
+
+ } else if (StringUtils.equals(node, PROPERTY)) {
+ processProperty(characteristic, cursor, messages);
+
+ } else if (StringUtils.equals(node, CHARACTERISTIC)) {
+ children.add(processCharacteristic(model, cursor, messages, technicalDebtRuleCache));
+
+ } else if (StringUtils.equals(node, "rule-repo")) {
+ ruleRepositoryKey = cursor.collectDescendantText().trim();
+
+ } else if (StringUtils.equals(node, "rule-key")) {
+ ruleKey = cursor.collectDescendantText().trim();
+ }
+ }
+ fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, technicalDebtRuleCache);
+
+ if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) {
+ addCharacteristicToModel(model, characteristic, children);
+ return characteristic;
+ }
+ return null;
+ }
+
+ private void fillRule(Characteristic characteristic, String ruleRepositoryKey, String ruleKey, ValidationMessages messages,
+ TechnicalDebtRuleCache technicalDebtRuleCache) {
+ if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
+ Rule rule = technicalDebtRuleCache.getRule(ruleRepositoryKey, ruleKey);
+ if (rule != null) {
+ characteristic.setRule(rule);
+ } else {
+ messages.addWarningText("Rule not found: [repository=" + ruleRepositoryKey + ", key=" + ruleKey + "]");
+ }
+ }
+ }
+
+ private void addCharacteristicToModel(Model model, Characteristic characteristic, List<Characteristic> children) {
+ model.addCharacteristic(characteristic);
+ for (Characteristic child : children) {
+ if (child != null) {
+ model.addCharacteristic(child);
+ characteristic.addChild(child);
+ }
+ }
+ }
+
+ private void processProperty(Characteristic characteristic, SMInputCursor cursor, ValidationMessages messages) throws XMLStreamException {
+ SMInputCursor c = cursor.childElementCursor();
+ String key = null;
+ Double value = null;
+ String textValue = null;
+ while (c.getNext() != null) {
+ String node = c.getLocalName();
+ if (StringUtils.equals(node, PROPERTY_KEY)) {
+ key = c.collectDescendantText().trim();
+
+ } else if (StringUtils.equals(node, PROPERTY_VALUE)) {
+ String s = c.collectDescendantText().trim();
+ try {
+ value = NumberUtils.createDouble(s);
+ } catch (NumberFormatException ex) {
+ String message = String.format("Cannot import value '%s' for field %s - Expected a numeric value instead", s, key);
+ LOG.error(message, ex);
+ messages.addErrorText(message);
+ }
+ } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
+ textValue = c.collectDescendantText().trim();
+ }
+ }
+ if (StringUtils.isNotBlank(key)) {
+ characteristic.setProperty(key, textValue).setValue(value);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.technicaldebt;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TechnicalDebtMergeModelTest {
+
+ private Model model;
+ private TechnicalDebtMergeModel technicalDebtMergeModel;
+ private List<Characteristic> defaultCharacteristics;
+
+ @Before
+ public void setUpModel() {
+ model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+ defaultCharacteristics = newArrayList();
+ technicalDebtMergeModel = new TechnicalDebtMergeModel(model, defaultCharacteristics);
+ }
+
+ @Test
+ public void merge_with_empty_model() {
+ Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+ Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
+ Characteristic ramEfficiency = with.createCharacteristicByKey("ram-efficiency", "RAM Efficiency");
+ efficiency.addChild(ramEfficiency);
+ Characteristic usability = with.createCharacteristicByKey("usability", "Usability");
+
+ ValidationMessages messages = ValidationMessages.create();
+
+ defaultCharacteristics.addAll(newArrayList(efficiency, ramEfficiency, usability));
+ technicalDebtMergeModel.mergeWith(with, messages, mockRuleCache());
+
+ assertThat(model.getCharacteristics()).hasSize(3);
+ assertThat(model.getRootCharacteristics()).hasSize(2);
+ assertThat(model.getCharacteristicByKey("ram-efficiency").getDepth()).isEqualTo(Characteristic.ROOT_DEPTH + 1);
+ assertThat(messages.getErrors()).isEmpty();
+ }
+
+ @Test
+ public void not_update_existing_characteristics() {
+ model.createCharacteristicByKey("efficiency", "Efficiency");
+
+ Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+ with.createCharacteristicByKey("efficiency", "New efficiency");
+
+ technicalDebtMergeModel.mergeWith(with, ValidationMessages.create(), mockRuleCache());
+
+ assertThat(model.getCharacteristics()).hasSize(1);
+ assertThat(model.getRootCharacteristics()).hasSize(1);
+ assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
+ }
+
+ @Test
+ public void warn_on_missing_rule() {
+ Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+ Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
+ Rule fooRule = Rule.create("foo", "bar", "Bar");
+ Characteristic requirement = with.createCharacteristicByRule(fooRule);
+ efficiency.addChild(requirement);
+
+ ValidationMessages messages = ValidationMessages.create();
+
+ defaultCharacteristics.add(efficiency);
+ technicalDebtMergeModel.mergeWith(with, messages, mockRuleCache());
+
+ assertThat(model.getCharacteristics()).hasSize(1);
+ assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
+ assertThat(model.getCharacteristicByRule(fooRule)).isNull();
+ assertThat(messages.getWarnings()).hasSize(1);
+ assertThat(messages.getWarnings().get(0)).contains("foo"); // warning: the rule foo does not exist
+ }
+
+ @Test
+ public void fail_when_adding_characteristic_not_existing_in_default_characteristics() {
+ Model with = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+ Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
+ // usability is not available in default characteristics
+ with.createCharacteristicByKey("usability", "Usability");
+
+ ValidationMessages messages = ValidationMessages.create();
+
+ defaultCharacteristics.add(efficiency);
+ try {
+ technicalDebtMergeModel.mergeWith(with, messages, mockRuleCache());
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class);
+ }
+ assertThat(model.getCharacteristics()).hasSize(1);
+ }
+
+ private TechnicalDebtRuleCache mockRuleCache() {
+ RuleFinder ruleFinder = mock(RuleFinder.class);
+ when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(newRegexpRule()));
+ return new TechnicalDebtRuleCache(ruleFinder);
+ }
+
+ private Rule newRegexpRule() {
+ return Rule.create("checkstyle", "regexp", "Regular expression");
+ }
+}
+
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.core.technicaldebt;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Resources;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.SonarPlugin;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TechnicalDebtModelFinderTest {
+
+ private static final String TEST_XML_PREFIX_PATH = "org/sonar/core/technicaldebt/TechnicalDebtModelFinderTest/";
+
+ private TechnicalDebtModelFinder modelFinder;
+
+ @Test
+ public void test_component_initialization() throws Exception {
+ // we do have the "csharp-model.xml" file in src/test/resources
+ PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
+ when(csharpPluginMetadata.getKey()).thenReturn("csharp");
+
+ // but we don' have the "php-model.xml" one
+ PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
+ when(phpPluginMetadata.getKey()).thenReturn("php");
+
+ PluginRepository repository = mock(PluginRepository.class);
+ when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
+ FakePlugin fakePlugin = new FakePlugin();
+ when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
+ modelFinder = new TechnicalDebtModelFinder(repository, TEST_XML_PREFIX_PATH);
+
+ // when
+ modelFinder.start();
+
+ // assert
+ Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+ assertThat(contributingPluginList.size()).isEqualTo(1);
+ assertThat(contributingPluginList).containsOnly("csharp");
+ }
+
+ @Test
+ public void contributing_plugin_list() throws Exception {
+ initModel();
+ Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+ assertThat(contributingPluginList.size()).isEqualTo(2);
+ assertThat(contributingPluginList).contains("csharp", "java");
+ }
+
+ @Test
+ public void get_content_for_xml_file() throws Exception {
+ initModel();
+ Reader xmlFileReader = null;
+ try {
+ xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
+ assertNotNull(xmlFileReader);
+ List<String> lines = IOUtils.readLines(xmlFileReader);
+ assertThat(lines.size()).isEqualTo(25);
+ assertThat(lines.get(0)).isEqualTo("<sqale>");
+ } catch (Exception e) {
+ fail("Should be able to read the XML file.");
+ } finally {
+ IOUtils.closeQuietly(xmlFileReader);
+ }
+ }
+
+ @Test
+ public void return_xml_file_path_for_plugin() throws Exception {
+ initModel();
+ assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
+ }
+
+ @Test
+ public void contain_default_model() throws Exception {
+ modelFinder = new TechnicalDebtModelFinder(mock(PluginRepository.class));
+ modelFinder.start();
+ assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
+ }
+
+ private void initModel() throws MalformedURLException {
+ Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
+ contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
+ contributingPluginKeyToClassLoader.put("java", newClassLoader());
+ modelFinder = new TechnicalDebtModelFinder(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
+ }
+
+ private ClassLoader newClassLoader() throws MalformedURLException {
+ ClassLoader loader = mock(ClassLoader.class);
+ when(loader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
+ public InputStream answer(InvocationOnMock invocation) throws Throwable {
+ return new FileInputStream(Resources.getResource((String) invocation.getArguments()[0]).getPath());
+ }
+ });
+ return loader;
+ }
+
+ class FakePlugin extends SonarPlugin {
+ public List getExtensions() {
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.technicaldebt;
+
+import com.google.common.collect.Lists;
+import org.fest.assertions.Assertions;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+
+import java.util.Collections;
+
+public class TechnicalDebtRuleCacheTest {
+
+ @Test
+ public void lazy_load_rules_on_first_call() throws Exception {
+
+ RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
+ Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Collections.EMPTY_LIST);
+
+ TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+ technicalDebtRuleCache.getRule("", "");
+ technicalDebtRuleCache.getRule("", "");
+
+ Mockito.verify(ruleFinder, Mockito.times(1)).findAll(Matchers.any(RuleQuery.class));
+ }
+
+ @Test
+ public void return_matching_rule() throws Exception {
+
+ Rule rule1 = Rule.create("repo1", "rule1");
+ Rule rule2 = Rule.create("repo2", "rule2");
+
+ RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
+ Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1, rule2));
+
+ TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+ Rule actualRule1 = technicalDebtRuleCache.getRule("repo1", "rule1");
+ Rule actualRule2 = technicalDebtRuleCache.getRule("repo2", "rule2");
+
+ Assertions.assertThat(actualRule1).isEqualTo(rule1);
+ Assertions.assertThat(actualRule2).isEqualTo(rule2);
+ }
+
+ @Test
+ public void return_if_rule_exists() throws Exception {
+
+ Rule rule1 = Rule.create("repo1", "rule1");
+ Rule rule2 = Rule.create("repo2", "rule2");
+
+ RuleFinder ruleFinder = Mockito.mock(RuleFinder.class);
+ Mockito.when(ruleFinder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1));
+
+ TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+
+ Assertions.assertThat(technicalDebtRuleCache.exists(rule1)).isTrue();
+ Assertions.assertThat(technicalDebtRuleCache.exists(rule2)).isFalse();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.technicaldebt;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Resources;
+import org.fest.assertions.Assertions;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.ValidationMessages;
+
+import java.io.IOException;
+
+public class TechnicalDebtXMLImporterTest {
+
+ @Test
+ public void shouldImportXML() {
+ TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+
+ String xml = getFileContent("shouldImportXML.xml");
+
+ ValidationMessages messages = ValidationMessages.create();
+ Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+ checkXmlCorrectlyImported(sqale, messages);
+ }
+
+ @Test
+ public void shouldBadlyFormattedImportXML() {
+ TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+ String xml = getFileContent("shouldImportXML_badly-formatted.xml");
+
+ ValidationMessages messages = ValidationMessages.create();
+ Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+ checkXmlCorrectlyImported(sqale, messages);
+ }
+
+ @Test
+ public void shouldLogWarningIfRuleNotFound() {
+ TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+ String xml = getFileContent("shouldLogWarningIfRuleNotFound.xml");
+ ValidationMessages messages = ValidationMessages.create();
+
+ Model sqale = new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+ Assertions.assertThat(messages.getWarnings()).hasSize(1);
+
+ // characteristics
+ Assertions.assertThat(sqale.getRootCharacteristics()).hasSize(1);
+ Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
+ Assertions.assertThat(efficiency.getChildren()).isEmpty();
+ Assertions.assertThat(messages.getWarnings().get(0)).contains("findbugs");
+ }
+
+ @Test
+ public void shouldNotifyOnUnexpectedValueTypeInXml() throws Exception {
+
+ TechnicalDebtRuleCache technicalDebtRuleCache = mockRuleCache();
+
+ String xml = getFileContent("shouldRejectXML_with_invalid_value.xml");
+ ValidationMessages messages = ValidationMessages.create();
+
+ new TechnicalDebtXMLImporter().importXML(xml, messages, technicalDebtRuleCache);
+
+ Assertions.assertThat(messages.getErrors()).hasSize(1);
+ Assertions.assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead");
+ }
+
+ private TechnicalDebtRuleCache mockRuleCache() {
+ RuleFinder finder = Mockito.mock(RuleFinder.class);
+ Mockito.when(finder.findAll(Matchers.any(RuleQuery.class))).thenReturn(Lists.newArrayList(Rule.create("checkstyle", "Regexp", "Regular expression")));
+ return new TechnicalDebtRuleCache(finder);
+ }
+
+ private void checkXmlCorrectlyImported(Model sqale, ValidationMessages messages) {
+
+ Assertions.assertThat(messages.getErrors()).isEmpty();
+ Assertions.assertThat(sqale.getName()).isEqualTo(TechnicalDebtModel.MODEL_NAME);
+
+ // characteristics
+ Assertions.assertThat(sqale.getRootCharacteristics()).hasSize(2);
+ Assertions.assertThat(sqale.getCharacteristicByKey("USABILITY").getDescription()).isEqualTo("Estimate usability");
+ Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
+ Assertions.assertThat(efficiency.getName()).isEqualTo("Efficiency");
+
+ // sub-characteristics
+ Assertions.assertThat(efficiency.getChildren()).hasSize(1);
+ Characteristic requirement = efficiency.getChildren().get(0);
+ Assertions.assertThat(requirement.getRule().getRepositoryKey()).isEqualTo("checkstyle");
+ Assertions.assertThat(requirement.getRule().getKey()).isEqualTo("Regexp");
+ Assertions.assertThat(requirement.getPropertyTextValue("function", null)).isEqualTo("linear");
+ Assertions.assertThat(requirement.getPropertyValue("factor", null)).isEqualTo(3.2);
+ }
+
+ private String getFileContent(String file) {
+ try {
+ return Resources.toString(Resources.getResource(TechnicalDebtXMLImporterTest.class, "TechnicalDebtXMLImporterTest/" + file), Charsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
--- /dev/null
+<sqale>
+ <chc>
+ <key>USABILITY</key>
+ <name>Usability</name>
+ <desc>Estimate usability</desc>
+ </chc>
+ <chc>
+ <key>EFFICIENCY</key>
+ <name>Efficiency</name>
+ <chc>
+ <rule-repo>gendarme</rule-repo>
+ <rule-key>EnsureLocalDisposalRule</rule-key>
+ <prop>
+ <key>remediationFactor</key>
+ <val>0.125</val>
+ <txt>d</txt>
+ </prop>
+ <prop>
+ <key>remediationFunction</key>
+ <txt>linear</txt>
+ </prop>
+ </chc>
+ </chc>
+
+</sqale>
\ No newline at end of file
--- /dev/null
+<sqale>
+ <chc>
+ <key>USABILITY</key>
+ <name>Usability</name>
+ <desc>Estimate usability</desc>
+ </chc>
+ <chc>
+ <key>EFFICIENCY</key>
+ <name>Efficiency</name>
+ <chc>
+ <rule-repo>squid-cobol</rule-repo>
+ <rule-key>CheckLoop</rule-key>
+ <prop>
+ <key>remediationFactor</key>
+ <val>0.125</val>
+ <txt>d</txt>
+ </prop>
+ <prop>
+ <key>remediationFunction</key>
+ <txt>linear</txt>
+ </prop>
+ </chc>
+ </chc>
+
+</sqale>
\ No newline at end of file
--- /dev/null
+<sqale>
+ <chc>
+ <key>USABILITY</key>
+ <name>Usability</name>
+ <desc>Estimate usability</desc>
+ </chc>
+ <chc>
+ <key>EFFICIENCY</key>
+ <name>Efficiency</name>
+
+ <chc>
+ <rule-repo>checkstyle</rule-repo>
+ <rule-key>Regexp</rule-key>
+ <prop>
+ <key>factor</key>
+ <val>3.2</val>
+ </prop>
+ <prop>
+ <key>function</key>
+ <txt>linear</txt>
+ </prop>
+ </chc>
+ </chc>
+
+</sqale>
\ No newline at end of file
--- /dev/null
+<sqale>
+ <chc>
+ <key>USABILITY
+ </key>
+ <name>Usability
+ </name>
+ <desc>Estimate usability
+ </desc>
+ </chc>
+ <chc>
+ <key>EFFICIENCY
+ </key>
+ <name>Efficiency
+ </name>
+
+ <chc>
+ <rule-repo>checkstyle
+ </rule-repo>
+ <rule-key>Regexp
+ </rule-key>
+ <prop>
+ <key>factor
+ </key>
+ <val>3.2
+ </val>
+ </prop>
+ <prop>
+ <key>function
+ </key>
+ <txt>linear
+ </txt>
+ </prop>
+ </chc>
+ </chc>
+
+</sqale>
\ No newline at end of file
--- /dev/null
+<sqale>
+ <chc>
+ <key>EFFICIENCY</key>
+ <name>Efficiency</name>
+
+ <chc>
+ <rule-repo>findbugs</rule-repo>
+ <rule-key>Foo</rule-key>
+ </chc>
+ </chc>
+
+</sqale>
\ No newline at end of file
--- /dev/null
+<sqale>
+ <chc>
+ <key>USABILITY</key>
+ <name>Usability</name>
+ <desc>Estimate usability</desc>
+ </chc>
+ <chc>
+ <key>EFFICIENCY</key>
+ <name>Efficiency</name>
+
+ <chc>
+ <rule-repo>checkstyle</rule-repo>
+ <rule-key>Regexp</rule-key>
+ <prop>
+ <key>factor</key>
+ <val>abc</val>
+ </prop>
+ <prop>
+ <key>function</key>
+ <txt>linear</txt>
+ </prop>
+ </chc>
+ </chc>
+
+</sqale>
import org.sonar.core.rule.DefaultRuleFinder;
import org.sonar.core.source.HtmlSourceDecorator;
import org.sonar.core.technicaldebt.TechnicalDebtConverter;
+import org.sonar.core.technicaldebt.TechnicalDebtModelFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtXMLImporter;
import org.sonar.core.test.TestPlanPerspectiveLoader;
import org.sonar.core.test.TestablePerspectiveLoader;
import org.sonar.core.timemachine.Periods;
import org.sonar.server.startup.*;
import org.sonar.server.technicaldebt.RubyTechnicalDebtService;
import org.sonar.server.technicaldebt.TechnicalDebtManager;
-import org.sonar.server.technicaldebt.TechnicalDebtModelFinder;
-import org.sonar.server.technicaldebt.XMLImporter;
import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.text.RubyTextService;
import org.sonar.server.ui.*;
// technical debt
servicesContainer.addSingleton(TechnicalDebtManager.class);
servicesContainer.addSingleton(TechnicalDebtModelFinder.class);
- servicesContainer.addSingleton(XMLImporter.class);
+ servicesContainer.addSingleton(TechnicalDebtXMLImporter.class);
servicesContainer.addSingleton(TechnicalDebtConverter.class);
servicesContainer.addSingleton(RubyTechnicalDebtService.class);
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.utils.TimeProfiler;
import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.technicaldebt.RuleCache;
+import org.sonar.core.technicaldebt.TechnicalDebtRuleCache;
import org.sonar.server.technicaldebt.TechnicalDebtManager;
public final class RegisterTechnicalDebtModel {
private static final Logger LOGGER = LoggerFactory.getLogger(RegisterTechnicalDebtModel.class);
- public static final String TECHNICAL_DEBT_MODEL = "TECHNICAL_DEBT";
-
private final TechnicalDebtManager technicalDebtManager;
private final RuleFinder ruleFinder;
public void start() {
TimeProfiler profiler = new TimeProfiler(LOGGER).start("Register Technical Debt Model");
- RuleCache ruleCache = new RuleCache(ruleFinder);
- technicalDebtManager.init(ValidationMessages.create(), ruleCache);
+ TechnicalDebtRuleCache technicalDebtRuleCache = new TechnicalDebtRuleCache(ruleFinder);
+ technicalDebtManager.init(ValidationMessages.create(), technicalDebtRuleCache);
profiler.stop();
}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.technicaldebt;
-
-import com.google.common.collect.Maps;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-
-import javax.annotation.CheckForNull;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-public class RuleCache {
-
- private final RuleFinder ruleFinder;
-
- private Map<String, Map<String, Rule>> cachedRules;
-
- public RuleCache(RuleFinder ruleFinder) {
- this.ruleFinder = ruleFinder;
- }
-
- @CheckForNull
- public Rule getRule(String repository, String ruleKey) {
- initRules();
- return lookUpRuleInCache(repository, ruleKey);
- }
-
- public boolean exists(Rule rule) {
- return getRule(rule.getRepositoryKey(), rule.getKey()) != null;
- }
-
- private void initRules(){
- if(cachedRules == null) {
- loadRules();
- }
- }
-
- private void loadRules() {
- cachedRules = Maps.newHashMap();
- Collection<Rule> rules = ruleFinder.findAll(RuleQuery.create());
- for (Rule rule : rules) {
- if(!cachedRules.containsKey(rule.getRepositoryKey())) {
- cachedRules.put(rule.getRepositoryKey(), new HashMap<String, Rule>());
- }
- Map<String, Rule> cachedRepository = cachedRules.get(rule.getRepositoryKey());
- if(!cachedRepository.containsKey(rule.getKey())) {
- cachedRepository.put(rule.getKey(), rule);
- }
- }
- }
-
- @CheckForNull
- private Rule lookUpRuleInCache(String repository, String ruleKey) {
- Map<String, Rule> cachedRepository = cachedRules.get(repository);
- if(cachedRepository != null) {
- return cachedRepository.get(ruleKey);
- }
- return null;
- }
-}
import org.sonar.api.qualitymodel.Model;
import org.sonar.api.qualitymodel.ModelFinder;
import org.sonar.api.utils.ValidationMessages;
+import org.sonar.core.technicaldebt.*;
import org.sonar.jpa.session.DatabaseSessionFactory;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
import java.io.Reader;
import java.util.Collection;
private DatabaseSessionFactory sessionFactory;
private ModelFinder modelFinder;
private TechnicalDebtModelFinder languageModelFinder;
- private XMLImporter importer;
+ private TechnicalDebtXMLImporter importer;
public TechnicalDebtManager(DatabaseSessionFactory sessionFactory, ModelFinder modelFinder,
- TechnicalDebtModelFinder languageModelFinder, XMLImporter importer) {
+ TechnicalDebtModelFinder languageModelFinder, TechnicalDebtXMLImporter importer) {
this.sessionFactory = sessionFactory;
this.modelFinder = modelFinder;
this.languageModelFinder = languageModelFinder;
this.importer = importer;
}
- public Model init(ValidationMessages messages, RuleCache rulesCache) {
+ public Model init(ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
DatabaseSession session = sessionFactory.getSession();
disableRequirementsOnRemovedRules(rulesCache);
return model;
}
- private Model loadOrCreateModelFromDb(Model defaultModel, ValidationMessages messages, RuleCache rulesCache) {
- Model model = modelFinder.findByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+ private Model loadOrCreateModelFromDb(Model defaultModel, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
+ Model model = modelFinder.findByName(TechnicalDebtModel.MODEL_NAME);
if (model == null) {
- model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+ model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
merge(defaultModel, model, defaultModel, messages, rulesCache);
}
return model;
}
- private void merge(Model pluginModel, Model existingModel, Model defaultModel, ValidationMessages messages, RuleCache rulesCache) {
+ private void merge(Model pluginModel, Model existingModel, Model defaultModel, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
messages.log(LOG);
if (!messages.hasErrors()) {
- new TechnicalDebtModel(existingModel, defaultModel.getCharacteristics()).mergeWith(pluginModel, messages, rulesCache);
+ new TechnicalDebtMergeModel(existingModel, defaultModel.getCharacteristics()).mergeWith(pluginModel, messages, rulesCache);
messages.log(LOG);
}
}
- private void loadRequirementsFromPlugins(Model existingModel, Model defaultModel, ValidationMessages messages, RuleCache rulesCache) {
+ private void loadRequirementsFromPlugins(Model existingModel, Model defaultModel, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
for (String pluginKey : getContributingPluginListWithoutSqale()) {
Model pluginModel = loadModelFromXml(pluginKey, messages, rulesCache);
merge(pluginModel, existingModel, defaultModel, messages, rulesCache);
}
}
- private Model loadModelFromXml(String pluginKey, ValidationMessages messages, RuleCache rulesCache) {
+ private Model loadModelFromXml(String pluginKey, ValidationMessages messages, TechnicalDebtRuleCache rulesCache) {
Reader xmlFileReader = null;
try {
xmlFileReader = languageModelFinder.createReaderForXMLFile(pluginKey);
/**
* Disable requirements linked on removed rules
*/
- private void disableRequirementsOnRemovedRules(RuleCache rulesCache) {
- Model existingModel = modelFinder.findByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+ private void disableRequirementsOnRemovedRules(TechnicalDebtRuleCache rulesCache) {
+ Model existingModel = modelFinder.findByName(TechnicalDebtModel.MODEL_NAME);
if (existingModel != null) {
for (Characteristic requirement : existingModel.getCharacteristicsByDepth(REQUIREMENT_LEVEL)) {
if (!rulesCache.exists(requirement.getRule())) {
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.technicaldebt;
-
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.CharacteristicProperty;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.utils.ValidationMessages;
-
-import java.util.List;
-
-public class TechnicalDebtModel {
-
- private Model model;
-
- private List<Characteristic> defaultCharacteristics;
-
- public TechnicalDebtModel(Model model, List<Characteristic> defaultCharacteristics) {
- this.model = model;
- this.defaultCharacteristics = defaultCharacteristics;
- }
-
- public void mergeWith(Model with, ValidationMessages messages, RuleCache ruleCache) {
- for (Characteristic characteristic : with.getCharacteristics()) {
- if (isRequirement(characteristic)) {
- mergeRequirement(characteristic, messages, ruleCache);
- } else {
- mergeCharacteristic(characteristic, messages);
- }
- }
- }
-
- private Characteristic mergeCharacteristic(Characteristic characteristic, ValidationMessages messages) {
- Characteristic existingCharacteristic = model.getCharacteristicByKey(characteristic.getKey());
- if (existingCharacteristic == null) {
- if (defaultCharacteristics.contains(characteristic)) {
- existingCharacteristic = model.addCharacteristic(clone(characteristic));
- if (!characteristic.getParents().isEmpty()) {
- Characteristic parentTargetCharacteristic = mergeCharacteristic(characteristic.getParents().get(0), messages);
- parentTargetCharacteristic.addChild(existingCharacteristic);
- }
- } else {
- throw new IllegalArgumentException("The characteristic : " + characteristic.getKey() + " cannot be used as it's not available in default ones.");
- }
- }
- return existingCharacteristic;
- }
-
- private void mergeRequirement(Characteristic requirement, ValidationMessages messages, RuleCache ruleCache) {
- Characteristic targetRequirement = model.getCharacteristicByRule(requirement.getRule());
- if (targetRequirement == null && !requirement.getParents().isEmpty()) {
- Rule rule = ruleCache.getRule(requirement.getRule().getRepositoryKey(), requirement.getRule().getKey());
- if (rule == null) {
- messages.addWarningText("The rule " + requirement.getRule() + " does not exist.");
-
- } else {
- Characteristic parent = mergeCharacteristic(requirement.getParents().get(0), messages);
- requirement = model.addCharacteristic(clone(requirement));
- requirement.setRule(rule);
- parent.addChild(requirement);
- }
- }
- }
-
- private boolean isRequirement(Characteristic characteristic) {
- return characteristic.hasRule();
- }
-
- private Characteristic clone(Characteristic c) {
- Characteristic clone = Characteristic.create();
- clone.setRule(c.getRule());
- clone.setDescription(c.getDescription());
- clone.setKey(c.getKey());
- clone.setName(c.getName(), false);
- for (CharacteristicProperty property : c.getProperties()) {
- clone.setProperty(property.getKey(), property.getTextValue()).setValue(property.getValue());
- }
- return clone;
- }
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-package org.sonar.server.technicaldebt;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
-import org.picocontainer.Startable;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-/**
- * <p>This class is used to find which technical debt model XML files exist in the Sonar instance.</p>
- * <p>
- * Those XML files are provided by language plugins that embed their own contribution to the definition of the Technical debt model.
- * They must be located in the classpath of those language plugins, more specifically in the "com.sonar.sqale" package, and
- * they must be named "<pluginKey>-model.xml".
- * </p>
- */
-public class TechnicalDebtModelFinder implements ServerExtension, Startable {
-
- public static final String DEFAULT_MODEL = "technical-debt";
-
- private static final String XML_FILE_SUFFIX = "-model.xml";
- private static final String XML_FILE_PREFIX = "com/sonar/sqale/";
-
- private String xmlFilePrefix;
-
- private PluginRepository pluginRepository;
- private Map<String, ClassLoader> contributingPluginKeyToClassLoader;
-
- public TechnicalDebtModelFinder(PluginRepository pluginRepository) {
- this.pluginRepository = pluginRepository;
- this.xmlFilePrefix = XML_FILE_PREFIX;
- }
-
- @VisibleForTesting
- TechnicalDebtModelFinder(PluginRepository pluginRepository, String xmlFilePrefix) {
- this.pluginRepository = pluginRepository;
- this.xmlFilePrefix = xmlFilePrefix;
- }
-
- @VisibleForTesting
- TechnicalDebtModelFinder(Map<String, ClassLoader> contributingPluginKeyToClassLoader, String xmlFilePrefix) {
- this.contributingPluginKeyToClassLoader = contributingPluginKeyToClassLoader;
- this.xmlFilePrefix = xmlFilePrefix;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void start() {
- findAvailableXMLFiles();
- }
-
- protected void findAvailableXMLFiles() {
- if (contributingPluginKeyToClassLoader == null) {
- contributingPluginKeyToClassLoader = Maps.newTreeMap();
- for (PluginMetadata metadata : pluginRepository.getMetadata()) {
- String pluginKey = metadata.getKey();
- ClassLoader classLoader = pluginRepository.getPlugin(pluginKey).getClass().getClassLoader();
- if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
- contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
- }
- }
- // Add default model
- contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
- }
- contributingPluginKeyToClassLoader = Collections.unmodifiableMap(contributingPluginKeyToClassLoader);
- }
-
- @VisibleForTesting
- String getXMLFilePath(String model) {
- return xmlFilePrefix + model + XML_FILE_SUFFIX;
- }
-
- /**
- * Returns the list of plugins that can contribute to the technical debt model (without the default model).
- *
- * @return the list of plugin keys
- */
- public Collection<String> getContributingPluginList() {
- Collection<String> contributingPlugins = newArrayList(contributingPluginKeyToClassLoader.keySet());
- contributingPlugins.remove(DEFAULT_MODEL);
- return contributingPlugins;
- }
-
- /**
- * Creates a new {@link java.io.Reader} for the XML file that contains the model contributed by the given plugin.
- *
- * @param pluginKey the key of the plugin that contributes the XML file
- * @return the reader, that must be closed once its use is finished.
- */
- public Reader createReaderForXMLFile(String pluginKey) {
- ClassLoader classLoader = contributingPluginKeyToClassLoader.get(pluginKey);
- String xmlFilePath = getXMLFilePath(pluginKey);
- return new InputStreamReader(classLoader.getResourceAsStream(xmlFilePath));
- }
-
- @VisibleForTesting
- Map<String, ClassLoader> getContributingPluginKeyToClassLoader(){
- return contributingPluginKeyToClassLoader;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void stop() {
- // Nothing to do
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.technicaldebt;
-
-import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.math.NumberUtils;
-import org.codehaus.stax2.XMLInputFactory2;
-import org.codehaus.staxmate.SMInputFactory;
-import org.codehaus.staxmate.in.SMHierarchicCursor;
-import org.codehaus.staxmate.in.SMInputCursor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
-
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.List;
-
-public class XMLImporter implements ServerExtension {
-
- private static final Logger LOG = LoggerFactory.getLogger(XMLImporter.class);
-
- private static final String CHARACTERISTIC = "chc";
- private static final String CHARACTERISTIC_KEY = "key";
- private static final String CHARACTERISTIC_NAME = "name";
- private static final String CHARACTERISTIC_DESCRIPTION = "desc";
- private static final String PROPERTY = "prop";
- private static final String PROPERTY_KEY = "key";
- private static final String PROPERTY_VALUE = "val";
- private static final String PROPERTY_TEXT_VALUE = "txt";
-
- public Model importXML(String xml, ValidationMessages messages, RuleCache ruleCache) {
- return importXML(new StringReader(xml), messages, ruleCache);
- }
-
- public Model importXML(Reader xml, ValidationMessages messages, RuleCache repositoryCache) {
- Model model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
- try {
- SMInputFactory inputFactory = initStax();
- SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);
-
- // advance to <sqale>
- cursor.advance();
- SMInputCursor chcCursor = cursor.childElementCursor(CHARACTERISTIC);
-
- while (chcCursor.getNext() != null) {
- processCharacteristic(model, chcCursor, messages, repositoryCache);
- }
-
- cursor.getStreamReader().closeCompletely();
-
- } catch (XMLStreamException e) {
- LOG.error("XML is not valid", e);
- messages.addErrorText("XML is not valid: " + e.getMessage());
- }
- return model;
- }
-
- private SMInputFactory initStax() {
- XMLInputFactory xmlFactory = XMLInputFactory2.newInstance();
- xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
- xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
- xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
- xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
- return new SMInputFactory(xmlFactory);
- }
-
- private Characteristic processCharacteristic(Model model, SMInputCursor chcCursor, ValidationMessages messages, RuleCache ruleCache) throws XMLStreamException {
- Characteristic characteristic = Characteristic.create();
- SMInputCursor cursor = chcCursor.childElementCursor();
-
- String ruleRepositoryKey = null, ruleKey = null;
- List<Characteristic> children = Lists.newArrayList();
- while (cursor.getNext() != null) {
- String node = cursor.getLocalName();
- if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
- characteristic.setKey(cursor.collectDescendantText().trim());
-
- } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
- characteristic.setName(cursor.collectDescendantText().trim(), false);
-
- } else if (StringUtils.equals(node, CHARACTERISTIC_DESCRIPTION)) {
- characteristic.setDescription(cursor.collectDescendantText().trim());
-
- } else if (StringUtils.equals(node, PROPERTY)) {
- processProperty(characteristic, cursor, messages);
-
- } else if (StringUtils.equals(node, CHARACTERISTIC)) {
- children.add(processCharacteristic(model, cursor, messages, ruleCache));
-
- } else if (StringUtils.equals(node, "rule-repo")) {
- ruleRepositoryKey = cursor.collectDescendantText().trim();
-
- } else if (StringUtils.equals(node, "rule-key")) {
- ruleKey = cursor.collectDescendantText().trim();
- }
- }
- fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, ruleCache);
-
- if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) {
- addCharacteristicToModel(model, characteristic, children);
- return characteristic;
- }
- return null;
- }
-
- private void fillRule(Characteristic characteristic, String ruleRepositoryKey, String ruleKey, ValidationMessages messages,
- RuleCache ruleCache) {
- if (StringUtils.isNotBlank(ruleRepositoryKey) && StringUtils.isNotBlank(ruleKey)) {
- Rule rule = ruleCache.getRule(ruleRepositoryKey, ruleKey);
- if (rule != null) {
- characteristic.setRule(rule);
- } else {
- messages.addWarningText("Rule not found: [repository=" + ruleRepositoryKey + ", key=" + ruleKey + "]");
- }
- }
- }
-
- private void addCharacteristicToModel(Model model, Characteristic characteristic, List<Characteristic> children) {
- model.addCharacteristic(characteristic);
- for (Characteristic child : children) {
- if (child != null) {
- model.addCharacteristic(child);
- characteristic.addChild(child);
- }
- }
- }
-
- private void processProperty(Characteristic characteristic, SMInputCursor cursor, ValidationMessages messages) throws XMLStreamException {
- SMInputCursor c = cursor.childElementCursor();
- String key = null;
- Double value = null;
- String textValue = null;
- while (c.getNext() != null) {
- String node = c.getLocalName();
- if (StringUtils.equals(node, PROPERTY_KEY)) {
- key = c.collectDescendantText().trim();
-
- } else if (StringUtils.equals(node, PROPERTY_VALUE)) {
- String s = c.collectDescendantText().trim();
- try {
- value = NumberUtils.createDouble(s);
- } catch (NumberFormatException ex) {
- String message = String.format("Cannot import value '%s' for field %s - Expected a numeric value instead", s, key);
- LOG.error(message, ex);
- messages.addErrorText(message);
- }
- } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
- textValue = c.collectDescendantText().trim();
- }
- }
- if (StringUtils.isNotBlank(key)) {
- characteristic.setProperty(key, textValue).setValue(value);
- }
- }
-}
import org.junit.Test;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.technicaldebt.RuleCache;
+import org.sonar.core.technicaldebt.TechnicalDebtRuleCache;
import org.sonar.server.technicaldebt.TechnicalDebtManager;
import static org.mockito.Matchers.any;
sqaleDefinition.start();
- verify(technicalDebtManager, times(1)).init(any(ValidationMessages.class), any(RuleCache.class));
+ verify(technicalDebtManager, times(1)).init(any(ValidationMessages.class), any(TechnicalDebtRuleCache.class));
}
}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.technicaldebt;
-
-import com.google.common.collect.Lists;
-import org.junit.Test;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-
-import java.util.Collections;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-public class RuleCacheTest {
-
- @Test
- public void lazy_load_rules_on_first_call() throws Exception {
-
- RuleFinder ruleFinder = mock(RuleFinder.class);
- when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(Collections.EMPTY_LIST);
-
- RuleCache ruleCache = new RuleCache(ruleFinder);
- ruleCache.getRule("", "");
- ruleCache.getRule("", "");
-
- verify(ruleFinder, times(1)).findAll(any(RuleQuery.class));
- }
-
- @Test
- public void return_matching_rule() throws Exception {
-
- Rule rule1 = Rule.create("repo1", "rule1");
- Rule rule2 = Rule.create("repo2", "rule2");
-
- RuleFinder ruleFinder = mock(RuleFinder.class);
- when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1, rule2));
-
- RuleCache ruleCache = new RuleCache(ruleFinder);
- Rule actualRule1 = ruleCache.getRule("repo1", "rule1");
- Rule actualRule2 = ruleCache.getRule("repo2", "rule2");
-
- assertThat(actualRule1).isEqualTo(rule1);
- assertThat(actualRule2).isEqualTo(rule2);
- }
-
- @Test
- public void return_if_rule_exists() throws Exception {
-
- Rule rule1 = Rule.create("repo1", "rule1");
- Rule rule2 = Rule.create("repo2", "rule2");
-
- RuleFinder ruleFinder = mock(RuleFinder.class);
- when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(Lists.newArrayList(rule1));
-
- RuleCache ruleCache = new RuleCache(ruleFinder);
-
- assertThat(ruleCache.exists(rule1)).isTrue();
- assertThat(ruleCache.exists(rule2)).isFalse();
- }
-}
import org.sonar.api.utils.ValidationMessages;
import org.sonar.core.qualitymodel.DefaultModelFinder;
import org.sonar.core.rule.DefaultRuleFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtModelFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtRuleCache;
+import org.sonar.core.technicaldebt.TechnicalDebtXMLImporter;
import org.sonar.jpa.test.AbstractDbUnitTestCase;
import java.io.FileNotFoundException;
when(technicalDebtModelFinder.createReaderForXMLFile("technical-debt")).thenReturn(
new FileReader(Resources.getResource(TechnicalDebtManagerTest.class, "TechnicalDebtManagerTest/fake-default-model.xml").getPath()));
- manager = new TechnicalDebtManager(getSessionFactory(), new DefaultModelFinder(getSessionFactory()), technicalDebtModelFinder, new XMLImporter());
+ manager = new TechnicalDebtManager(getSessionFactory(), new DefaultModelFinder(getSessionFactory()), technicalDebtModelFinder, new TechnicalDebtXMLImporter());
}
@Test
addPluginModel("java", "fake-java-model.xml");
- RuleCache ruleCache = mock(RuleCache.class);
+ TechnicalDebtRuleCache technicalDebtRuleCache = mock(TechnicalDebtRuleCache.class);
Rule rule1 = Rule.create("checkstyle", "import", "Regular expression");
rule1.setId(1);
- when(ruleCache.getRule("checkstyle", "import")).thenReturn(rule1);
+ when(technicalDebtRuleCache.getRule("checkstyle", "import")).thenReturn(rule1);
Rule rule2 = Rule.create("checkstyle", "export", "Regular expression");
rule2.setId(2);
- when(ruleCache.getRule("checkstyle", "export")).thenReturn(rule2);
+ when(technicalDebtRuleCache.getRule("checkstyle", "export")).thenReturn(rule2);
- manager.init(ValidationMessages.create(), ruleCache);
+ manager.init(ValidationMessages.create(), technicalDebtRuleCache);
checkTables("create_model_with_requirements_from_plugin_on_first_execution", "quality_models", "characteristics", "characteristic_edges", "characteristic_properties");
}
addPluginModel("java", "fake-java-model.xml");
- RuleCache ruleCache = mock(RuleCache.class);
+ TechnicalDebtRuleCache technicalDebtRuleCache = mock(TechnicalDebtRuleCache.class);
Rule rule1 = Rule.create("checkstyle", "import", "Regular expression");
rule1.setId(1);
- when(ruleCache.getRule("checkstyle", "import")).thenReturn(rule1);
+ when(technicalDebtRuleCache.getRule("checkstyle", "import")).thenReturn(rule1);
Rule rule2 = Rule.create("checkstyle", "export", "Regular expression");
rule2.setId(2);
- when(ruleCache.getRule("checkstyle", "export")).thenReturn(rule2);
+ when(technicalDebtRuleCache.getRule("checkstyle", "export")).thenReturn(rule2);
ValidationMessages messages = ValidationMessages.create();
- manager.init(messages, ruleCache);
+ manager.init(messages, technicalDebtRuleCache);
assertThat(messages.getWarnings()).hasSize(1);
assertThat(messages.getWarnings().get(0)).isEqualTo("Rule not found: [repository=checkstyle, key=ConstantNameCheck]");
}
- private RuleCache defaultRuleCache() {
- return new RuleCache(new DefaultRuleFinder(getSessionFactory()));
+ private TechnicalDebtRuleCache defaultRuleCache() {
+ return new TechnicalDebtRuleCache(new DefaultRuleFinder(getSessionFactory()));
}
private void addPluginModel(String pluginKey, String xmlFile) throws FileNotFoundException {
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-package org.sonar.server.technicaldebt;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Resources;
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.SonarPlugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.Reader;
-import java.net.MalformedURLException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TechnicalDebtModelFinderTest {
-
- private static final String TEST_XML_PREFIX_PATH = "org/sonar/server/technicaldebt/TechnicalDebtModelFinderTest/";
-
- private TechnicalDebtModelFinder modelFinder;
-
- @Test
- public void test_component_initialization() throws Exception {
- // we do have the "csharp-model.xml" file in src/test/resources
- PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
- when(csharpPluginMetadata.getKey()).thenReturn("csharp");
-
- // but we don' have the "php-model.xml" one
- PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
- when(phpPluginMetadata.getKey()).thenReturn("php");
-
- PluginRepository repository = mock(PluginRepository.class);
- when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
- FakePlugin fakePlugin = new FakePlugin();
- when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
- modelFinder = new TechnicalDebtModelFinder(repository, TEST_XML_PREFIX_PATH);
-
- // when
- modelFinder.start();
-
- // assert
- Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
- assertThat(contributingPluginList.size()).isEqualTo(1);
- assertThat(contributingPluginList).containsOnly("csharp");
- }
-
- @Test
- public void contributing_plugin_list() throws Exception {
- initModel();
- Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
- assertThat(contributingPluginList.size()).isEqualTo(2);
- assertThat(contributingPluginList).contains("csharp", "java");
- }
-
- @Test
- public void get_content_for_xml_file() throws Exception {
- initModel();
- Reader xmlFileReader = null;
- try {
- xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
- assertNotNull(xmlFileReader);
- List<String> lines = IOUtils.readLines(xmlFileReader);
- assertThat(lines.size()).isEqualTo(25);
- assertThat(lines.get(0)).isEqualTo("<sqale>");
- } catch (Exception e) {
- fail("Should be able to read the XML file.");
- } finally {
- IOUtils.closeQuietly(xmlFileReader);
- }
- }
-
- @Test
- public void return_xml_file_path_for_plugin() throws Exception {
- initModel();
- assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
- }
-
- @Test
- public void contain_default_model() throws Exception {
- modelFinder = new TechnicalDebtModelFinder(mock(PluginRepository.class));
- modelFinder.start();
- assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
- }
-
- private void initModel() throws MalformedURLException {
- Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
- contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
- contributingPluginKeyToClassLoader.put("java", newClassLoader());
- modelFinder = new TechnicalDebtModelFinder(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
- }
-
- private ClassLoader newClassLoader() throws MalformedURLException {
- ClassLoader loader = mock(ClassLoader.class);
- when(loader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
- public InputStream answer(InvocationOnMock invocation) throws Throwable {
- return new FileInputStream(Resources.getResource((String) invocation.getArguments()[0]).getPath());
- }
- });
- return loader;
- }
-
- class FakePlugin extends SonarPlugin {
- public List getExtensions() {
- return null;
- }
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.technicaldebt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
-
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TechnicalDebtModelTest {
-
- private Model model;
- private TechnicalDebtModel technicalDebtModel;
- private List<Characteristic> defaultCharacteristics;
-
- @Before
- public void setUpModel() {
- model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
- defaultCharacteristics = newArrayList();
- technicalDebtModel = new TechnicalDebtModel(model, defaultCharacteristics);
- }
-
- @Test
- public void merge_with_empty_model() {
- Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
- Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
- Characteristic ramEfficiency = with.createCharacteristicByKey("ram-efficiency", "RAM Efficiency");
- efficiency.addChild(ramEfficiency);
- Characteristic usability = with.createCharacteristicByKey("usability", "Usability");
-
- ValidationMessages messages = ValidationMessages.create();
-
- defaultCharacteristics.addAll(newArrayList(efficiency, ramEfficiency, usability));
- technicalDebtModel.mergeWith(with, messages, mockRuleCache());
-
- assertThat(model.getCharacteristics()).hasSize(3);
- assertThat(model.getRootCharacteristics()).hasSize(2);
- assertThat(model.getCharacteristicByKey("ram-efficiency").getDepth()).isEqualTo(Characteristic.ROOT_DEPTH + 1);
- assertThat(messages.getErrors()).isEmpty();
- }
-
- @Test
- public void not_update_existing_characteristics() {
- model.createCharacteristicByKey("efficiency", "Efficiency");
-
- Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
- with.createCharacteristicByKey("efficiency", "New efficiency");
-
- technicalDebtModel.mergeWith(with, ValidationMessages.create(), mockRuleCache());
-
- assertThat(model.getCharacteristics()).hasSize(1);
- assertThat(model.getRootCharacteristics()).hasSize(1);
- assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
- }
-
- @Test
- public void warn_on_missing_rule() {
- Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
- Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
- Rule fooRule = Rule.create("foo", "bar", "Bar");
- Characteristic requirement = with.createCharacteristicByRule(fooRule);
- efficiency.addChild(requirement);
-
- ValidationMessages messages = ValidationMessages.create();
-
- defaultCharacteristics.add(efficiency);
- technicalDebtModel.mergeWith(with, messages, mockRuleCache());
-
- assertThat(model.getCharacteristics()).hasSize(1);
- assertThat(model.getCharacteristicByKey("efficiency").getName()).isEqualTo("Efficiency");
- assertThat(model.getCharacteristicByRule(fooRule)).isNull();
- assertThat(messages.getWarnings()).hasSize(1);
- assertThat(messages.getWarnings().get(0)).contains("foo"); // warning: the rule foo does not exist
- }
-
- @Test
- public void fail_when_adding_characteristic_not_existing_in_default_characteristics() {
- Model with = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
- Characteristic efficiency = with.createCharacteristicByKey("efficiency", "Efficiency");
- // usability is not available in default characteristics
- with.createCharacteristicByKey("usability", "Usability");
-
- ValidationMessages messages = ValidationMessages.create();
-
- defaultCharacteristics.add(efficiency);
- try {
- technicalDebtModel.mergeWith(with, messages, mockRuleCache());
- fail();
- } catch (Exception e) {
- assertThat(e).isInstanceOf(IllegalArgumentException.class);
- }
- assertThat(model.getCharacteristics()).hasSize(1);
- }
-
- private RuleCache mockRuleCache() {
- RuleFinder ruleFinder = mock(RuleFinder.class);
- when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(newRegexpRule()));
- return new RuleCache(ruleFinder);
- }
-
- private Rule newRegexpRule() {
- return Rule.create("checkstyle", "regexp", "Regular expression");
- }
-}
-
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.technicaldebt;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.io.Resources;
-import org.junit.Test;
-import org.sonar.api.qualitymodel.Characteristic;
-import org.sonar.api.qualitymodel.Model;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.server.startup.RegisterTechnicalDebtModel;
-
-import java.io.IOException;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class XMLImporterTest {
-
- @Test
- public void shouldImportXML() {
- RuleCache ruleCache = mockRuleCache();
-
- String xml = getFileContent("shouldImportXML.xml");
-
- ValidationMessages messages = ValidationMessages.create();
- Model sqale = new XMLImporter().importXML(xml, messages, ruleCache);
-
- checkXmlCorrectlyImported(sqale, messages);
- }
-
- @Test
- public void shouldBadlyFormattedImportXML() {
- RuleCache ruleCache = mockRuleCache();
- String xml = getFileContent("shouldImportXML_badly-formatted.xml");
-
- ValidationMessages messages = ValidationMessages.create();
- Model sqale = new XMLImporter().importXML(xml, messages, ruleCache);
-
- checkXmlCorrectlyImported(sqale, messages);
- }
-
- @Test
- public void shouldLogWarningIfRuleNotFound() {
- RuleCache ruleCache = mockRuleCache();
- String xml = getFileContent("shouldLogWarningIfRuleNotFound.xml");
- ValidationMessages messages = ValidationMessages.create();
-
- Model sqale = new XMLImporter().importXML(xml, messages, ruleCache);
-
- assertThat(messages.getWarnings()).hasSize(1);
-
- // characteristics
- assertThat(sqale.getRootCharacteristics()).hasSize(1);
- Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
- assertThat(efficiency.getChildren()).isEmpty();
- assertThat(messages.getWarnings().get(0)).contains("findbugs");
- }
-
- @Test
- public void shouldNotifyOnUnexpectedValueTypeInXml() throws Exception {
-
- RuleCache ruleCache = mockRuleCache();
-
- String xml = getFileContent("shouldRejectXML_with_invalid_value.xml");
- ValidationMessages messages = ValidationMessages.create();
-
- new XMLImporter().importXML(xml, messages, ruleCache);
-
- assertThat(messages.getErrors()).hasSize(1);
- assertThat(messages.getErrors().get(0)).isEqualTo("Cannot import value 'abc' for field factor - Expected a numeric value instead");
- }
-
- private RuleCache mockRuleCache() {
- RuleFinder finder = mock(RuleFinder.class);
- when(finder.findAll(any(RuleQuery.class))).thenReturn(Lists.newArrayList(Rule.create("checkstyle", "Regexp", "Regular expression")));
- return new RuleCache(finder);
- }
-
- private void checkXmlCorrectlyImported(Model sqale, ValidationMessages messages) {
-
- assertThat(messages.getErrors()).isEmpty();
- assertThat(sqale.getName()).isEqualTo(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
-
- // characteristics
- assertThat(sqale.getRootCharacteristics()).hasSize(2);
- assertThat(sqale.getCharacteristicByKey("USABILITY").getDescription()).isEqualTo("Estimate usability");
- Characteristic efficiency = sqale.getCharacteristicByKey("EFFICIENCY");
- assertThat(efficiency.getName()).isEqualTo("Efficiency");
-
- // sub-characteristics
- assertThat(efficiency.getChildren()).hasSize(1);
- Characteristic requirement = efficiency.getChildren().get(0);
- assertThat(requirement.getRule().getRepositoryKey()).isEqualTo("checkstyle");
- assertThat(requirement.getRule().getKey()).isEqualTo("Regexp");
- assertThat(requirement.getPropertyTextValue("function", null)).isEqualTo("linear");
- assertThat(requirement.getPropertyValue("factor", null)).isEqualTo(3.2);
- }
-
- private String getFileContent(String file) {
- try {
- return Resources.toString(Resources.getResource(XMLImporterTest.class, "XMLImporterTest/" + file), Charsets.UTF_8);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
-}
+++ /dev/null
-<sqale>
- <chc>
- <key>USABILITY</key>
- <name>Usability</name>
- <desc>Estimate usability</desc>
- </chc>
- <chc>
- <key>EFFICIENCY</key>
- <name>Efficiency</name>
- <chc>
- <rule-repo>gendarme</rule-repo>
- <rule-key>EnsureLocalDisposalRule</rule-key>
- <prop>
- <key>remediationFactor</key>
- <val>0.125</val>
- <txt>d</txt>
- </prop>
- <prop>
- <key>remediationFunction</key>
- <txt>linear</txt>
- </prop>
- </chc>
- </chc>
-
-</sqale>
\ No newline at end of file
+++ /dev/null
-<sqale>
- <chc>
- <key>USABILITY</key>
- <name>Usability</name>
- <desc>Estimate usability</desc>
- </chc>
- <chc>
- <key>EFFICIENCY</key>
- <name>Efficiency</name>
- <chc>
- <rule-repo>squid-cobol</rule-repo>
- <rule-key>CheckLoop</rule-key>
- <prop>
- <key>remediationFactor</key>
- <val>0.125</val>
- <txt>d</txt>
- </prop>
- <prop>
- <key>remediationFunction</key>
- <txt>linear</txt>
- </prop>
- </chc>
- </chc>
-
-</sqale>
\ No newline at end of file
+++ /dev/null
-<sqale>
- <chc>
- <key>USABILITY</key>
- <name>Usability</name>
- <desc>Estimate usability</desc>
- </chc>
- <chc>
- <key>EFFICIENCY</key>
- <name>Efficiency</name>
-
- <chc>
- <rule-repo>checkstyle</rule-repo>
- <rule-key>Regexp</rule-key>
- <prop>
- <key>factor</key>
- <val>3.2</val>
- </prop>
- <prop>
- <key>function</key>
- <txt>linear</txt>
- </prop>
- </chc>
- </chc>
-
-</sqale>
\ No newline at end of file
+++ /dev/null
-<sqale>
- <chc>
- <key>USABILITY
- </key>
- <name>Usability
- </name>
- <desc>Estimate usability
- </desc>
- </chc>
- <chc>
- <key>EFFICIENCY
- </key>
- <name>Efficiency
- </name>
-
- <chc>
- <rule-repo>checkstyle
- </rule-repo>
- <rule-key>Regexp
- </rule-key>
- <prop>
- <key>factor
- </key>
- <val>3.2
- </val>
- </prop>
- <prop>
- <key>function
- </key>
- <txt>linear
- </txt>
- </prop>
- </chc>
- </chc>
-
-</sqale>
\ No newline at end of file
+++ /dev/null
-<sqale>
- <chc>
- <key>EFFICIENCY</key>
- <name>Efficiency</name>
-
- <chc>
- <rule-repo>findbugs</rule-repo>
- <rule-key>Foo</rule-key>
- </chc>
- </chc>
-
-</sqale>
\ No newline at end of file
+++ /dev/null
-<sqale>
- <chc>
- <key>USABILITY</key>
- <name>Usability</name>
- <desc>Estimate usability</desc>
- </chc>
- <chc>
- <key>EFFICIENCY</key>
- <name>Efficiency</name>
-
- <chc>
- <rule-repo>checkstyle</rule-repo>
- <rule-key>Regexp</rule-key>
- <prop>
- <key>factor</key>
- <val>abc</val>
- </prop>
- <prop>
- <key>function</key>
- <txt>linear</txt>
- </prop>
- </chc>
- </chc>
-
-</sqale>