]> source.dussan.org Git - sonarqube.git/commitdiff
Revert "Remove all deprecated Violation APIs"
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 27 Mar 2015 17:17:09 +0000 (18:17 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 27 Mar 2015 17:17:09 +0000 (18:17 +0100)
This reverts commit 5446d877b4e67f2f32ac869e76d9ad02ca226773.

13 files changed:
sonar-batch/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java
sonar-batch/src/main/java/org/sonar/batch/deprecated/decorator/DefaultDecoratorContext.java
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java
sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/DecoratorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/SensorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java
sonar-plugin-api/src/main/java/org/sonar/api/rules/Violation.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/rules/ViolationTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/test/IsViolation.java [new file with mode: 0644]

index b22660d590f216682f56b91ca51b1b56d9b557b9..2a70c13ac9d301e4b9e526604ef83141f494351c 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.resources.*;
+import org.sonar.api.rules.Violation;
 import org.sonar.api.utils.SonarException;
 import org.sonar.batch.sensor.DefaultSensorContext;
 import org.sonar.batch.sensor.coverage.CoverageExclusions;
@@ -181,6 +182,28 @@ public class DeprecatedSensorContext extends DefaultSensorContext implements Sen
     }
   }
 
+  @Override
+  public void saveViolation(Violation violation, boolean force) {
+    if (violation.getResource() == null) {
+      violation.setResource(resourceOrProject(violation.getResource()));
+    }
+    index.addViolation(violation, force);
+  }
+
+  @Override
+  public void saveViolation(Violation violation) {
+    saveViolation(violation, false);
+  }
+
+  @Override
+  public void saveViolations(Collection<Violation> violations) {
+    if (violations != null) {
+      for (Violation violation : violations) {
+        saveViolation(violation);
+      }
+    }
+  }
+
   @Override
   public Dependency saveDependency(Dependency dependency) {
     return index.addDependency(dependency);
index 0b63501eb805498dc6cefc8bca68ed77c1ceac42..47d75f4434d16ab2440ddfcf8b7bc26a58705e45 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.api.design.Dependency;
 import org.sonar.api.measures.*;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
 import org.sonar.api.utils.SonarException;
 import org.sonar.batch.duplication.DuplicationCache;
 import org.sonar.batch.duplication.DuplicationUtils;
@@ -213,4 +214,18 @@ public class DefaultDecoratorContext implements DecoratorContext {
   public Collection<Dependency> getOutgoingDependencies() {
     return sonarIndex.getOutgoingEdges(resource);
   }
+
+  @Override
+  public DefaultDecoratorContext saveViolation(Violation violation, boolean force) {
+    if (violation.getResource() == null) {
+      violation.setResource(resource);
+    }
+    sonarIndex.addViolation(violation, force);
+    return this;
+  }
+
+  @Override
+  public DefaultDecoratorContext saveViolation(Violation violation) {
+    return saveViolation(violation, false);
+  }
 }
index 7a12b86eaabb0c815e32b708228384a46e122661..33f6246ea8cd1532192a3f6cfac73ec06e813d44 100644 (file)
@@ -38,6 +38,8 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.MeasuresFilters;
 import org.sonar.api.resources.*;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.api.utils.SonarException;
 import org.sonar.batch.ProjectTree;
@@ -359,6 +361,44 @@ public class DefaultIndex extends SonarIndex {
     return result;
   }
 
+  //
+  //
+  //
+  // VIOLATIONS
+  //
+  //
+  //
+
+  @Override
+  public void addViolation(Violation violation, boolean force) {
+    Resource resource = violation.getResource();
+    if (resource == null) {
+      violation.setResource(currentProject);
+    } else if (!Scopes.isHigherThanOrEquals(resource, Scopes.FILE)) {
+      throw new IllegalArgumentException("Violations are only supported on files, directories and project");
+    }
+
+    Rule rule = violation.getRule();
+    if (rule == null) {
+      LOG.warn("Rule is null. Ignoring violation {}", violation);
+      return;
+    }
+
+    Bucket bucket = getBucket(resource);
+    if (bucket == null) {
+      LOG.warn("Resource is not indexed. Ignoring violation {}", violation);
+      return;
+    }
+
+    // keep a limitation (bug?) of deprecated violations api : severity is always
+    // set by sonar. The severity set by plugins is overridden.
+    // This is not the case with issue api.
+    violation.setSeverity(null);
+
+    violation.setResource(bucket.getResource());
+    moduleIssues.initAndAddViolation(violation);
+  }
+
   @Override
   public String getSource(Resource reference) {
     Resource resource = getResource(reference);
index 94938954ebfe43af3150b48ad8290b0b05688846..64f7e8d2446f31038ef97969a879d7527155ab2a 100644 (file)
@@ -30,8 +30,10 @@ import org.sonar.api.batch.rule.internal.DefaultActiveRule;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.resources.Project;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Violation;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.MessageException;
+import org.sonar.core.issue.DefaultIssueBuilder;
 
 import javax.annotation.Nullable;
 
@@ -46,7 +48,7 @@ public class ModuleIssues {
   private final Project project;
   private final IssueFilters filters;
 
-  public ModuleIssues(ActiveRules activeRules, @Nullable Rules rules, IssueCache cache, Project project, IssueFilters filters) {
+  public ModuleIssues(ActiveRules activeRules, @Nullable Rules rules, IssueCache cache, @Nullable Project project, IssueFilters filters) {
     this.activeRules = activeRules;
     this.rules = rules;
     this.cache = cache;
@@ -54,10 +56,35 @@ public class ModuleIssues {
     this.filters = filters;
   }
 
-  public ModuleIssues(ActiveRules activeRules, IssueCache cache, Project project, IssueFilters filters) {
+  public ModuleIssues(ActiveRules activeRules, IssueCache cache, @Nullable Project project, IssueFilters filters) {
     this(activeRules, null, cache, project, filters);
   }
 
+  /** 
+   * Used by scan2
+   */
+  public ModuleIssues(ActiveRules activeRules, Rules rules, IssueCache cache, IssueFilters filters) {
+    this(activeRules, rules, cache, null, filters);
+  }
+
+  public boolean initAndAddViolation(Violation violation) {
+    DefaultIssue issue = newIssue(violation);
+    return initAndAddIssue(issue);
+  }
+
+  private DefaultIssue newIssue(Violation violation) {
+    return new DefaultIssueBuilder()
+      .componentKey(violation.getResource().getEffectiveKey())
+      // Project can be null but Violation not used by scan2
+      .projectKey(project.getRoot().getEffectiveKey())
+      .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
+      .effortToFix(violation.getCost())
+      .line(violation.getLineId())
+      .message(violation.getMessage())
+      .severity(violation.getSeverity() != null ? violation.getSeverity().name() : null)
+      .build();
+  }
+
   public boolean initAndAddIssue(DefaultIssue issue) {
     RuleKey ruleKey = issue.ruleKey();
     Rule rule = null;
@@ -92,8 +119,10 @@ public class ModuleIssues {
     if (Strings.isNullOrEmpty(issue.message())) {
       issue.setMessage(((DefaultActiveRule) activeRule).name());
     }
-    issue.setCreationDate(project.getAnalysisDate());
-    issue.setUpdateDate(project.getAnalysisDate());
+    if (project != null) {
+      issue.setCreationDate(project.getAnalysisDate());
+      issue.setUpdateDate(project.getAnalysisDate());
+    }
     if (issue.severity() == null) {
       issue.setSeverity(activeRule.severity());
     }
index 1895e6d103bedd1447c2e11795708ae108a90332..c886d93bf51c6214ab02d6bb64a10df8328a7f3d 100644 (file)
@@ -23,7 +23,6 @@ package org.sonar.batch.rule;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ListMultimap;
 import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.batch.RequiresDB;
 import org.sonar.api.batch.debt.DebtCharacteristic;
 import org.sonar.api.batch.debt.DebtModel;
 import org.sonar.api.batch.debt.DebtRemediationFunction;
@@ -40,13 +39,11 @@ import org.sonar.core.rule.RuleDto;
 import org.sonar.core.rule.RuleParamDto;
 
 import javax.annotation.Nullable;
-
 import java.util.List;
 
 /**
  * Loads all enabled and non manual rules
  */
-@RequiresDB
 public class RulesProvider extends ProviderAdapter {
 
   private Rules singleton = null;
index 6b33dab0611efeeed24f3d1fbd006b71a33a04c2..7ffa990572183c408ce32c438932dfba42ee507d 100644 (file)
@@ -30,9 +30,13 @@ import org.sonar.api.batch.debt.DebtRemediationFunction;
 import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
 import org.sonar.api.batch.rule.internal.RulesBuilder;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.rules.Violation;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.MessageException;
 
@@ -41,6 +45,7 @@ import java.util.Date;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -195,6 +200,36 @@ public class ModuleIssuesTest {
     assertThat(argument.getValue().message()).isEqualTo("Avoid Cycle");
   }
 
+  @Test
+  public void add_deprecated_violation() throws Exception {
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate();
+    initModuleIssues();
+
+    org.sonar.api.rules.Rule rule = org.sonar.api.rules.Rule.create("squid", "AvoidCycle", "Avoid Cycle");
+    Resource resource = File.create("org/struts/Action.java").setEffectiveKey("struts:src/org/struts/Action.java");
+    Violation violation = new Violation(rule, resource);
+    violation.setLineId(42);
+    violation.setSeverity(RulePriority.CRITICAL);
+    violation.setMessage("the message");
+
+    when(filters.accept(any(DefaultIssue.class))).thenReturn(true);
+
+    boolean added = moduleIssues.initAndAddViolation(violation);
+    assertThat(added).isTrue();
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(cache).put(argument.capture());
+    DefaultIssue issue = argument.getValue();
+    assertThat(issue.severity()).isEqualTo(Severity.CRITICAL);
+    assertThat(issue.line()).isEqualTo(42);
+    assertThat(issue.message()).isEqualTo("the message");
+    assertThat(issue.key()).isNotEmpty();
+    assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
+    assertThat(issue.componentKey()).isEqualTo("struts:src/org/struts/Action.java");
+    assertThat(issue.projectKey()).isEqualTo("org.apache:struts-core");
+  }
+
   @Test
   public void filter_issue() throws Exception {
     ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
index ae7f9dd30691255b622fd4e290862e6461c8dac7..018d22cc0a0e2c58ed278d865c923311940ea359 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
 
 import java.util.Collection;
 import java.util.List;
@@ -96,4 +97,23 @@ public interface DecoratorContext {
 
   Collection<Dependency> getOutgoingDependencies();
 
+  // RULES
+
+  /**
+   * Save a coding rule violation. The decorator which calls this method must be depended upon BatchBarriers.END_OF_VIOLATIONS_GENERATION.
+   * 
+   * @since 2.5
+   * @param force allows to force creation of violation even if it was suppressed by {@link org.sonar.api.rules.ViolationFilter}
+   * @deprecated in 3.6, replaced by {@link org.sonar.api.issue.Issuable}
+   */
+  @Deprecated
+  DecoratorContext saveViolation(Violation violation, boolean force);
+
+  /**
+   * Save a coding rule violation. The decorator which calls this method must be depended upon BatchBarriers.END_OF_VIOLATIONS_GENERATION.
+   * @deprecated in 3.6, replaced by {@link org.sonar.api.issue.Issuable}
+   */
+  @Deprecated
+  DecoratorContext saveViolation(Violation violation);
+
 }
index 28004e4b239ae8d6256bcbd075ed2c8d552283e4..f5b73a257225b0e9f906c454a1139cbc129ab989 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
 
 import javax.annotation.CheckForNull;
 
@@ -162,6 +163,32 @@ public interface SensorContext extends org.sonar.api.batch.sensor.SensorContext
    */
   Measure saveMeasure(Resource resource, Measure measure);
 
+  // ----------- RULE VIOLATIONS --------------
+
+  /**
+   * Save a coding rule violation.
+   *
+   * @param force allows to force creation of violation even if it was supressed by {@link org.sonar.api.rules.ViolationFilter}
+   * @since 2.5
+   * @deprecated in 3.6, replaced by {@link org.sonar.api.issue.Issuable}
+   */
+  @Deprecated
+  void saveViolation(Violation violation, boolean force);
+
+  /**
+   * Save a coding rule violation.
+   * @deprecated in 3.6, replaced by {@link org.sonar.api.issue.Issuable}
+   */
+  @Deprecated
+  void saveViolation(Violation violation);
+
+  /**
+   * Saves a list of violations.
+   * @deprecated in 3.6, replaced by {@link org.sonar.api.issue.Issuable}
+   */
+  @Deprecated
+  void saveViolations(Collection<Violation> violations);
+
   // ----------- DEPENDENCIES BETWEEN RESOURCES --------------
 
   Dependency saveDependency(Dependency dependency);
index 6384ef65edc9c30a5ce8c70baaa73f6ba834c28e..5032c68afcc6bd2672d944689a67a6b3f2f77f91 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Violation;
 import org.sonar.graph.DirectedGraphAccessor;
 
 import javax.annotation.CheckForNull;
@@ -118,6 +119,21 @@ public abstract class SonarIndex implements DirectedGraphAccessor<Resource, Depe
   @CheckForNull
   public abstract <M> M getMeasures(Resource resource, MeasuresFilter<M> filter);
 
+  /**
+   * @since 2.5
+   * @deprecated in 3.6
+   */
+  @Deprecated
+  public abstract void addViolation(Violation violation, boolean force);
+
+  /**
+   * @deprecated in 3.6
+   */
+  @Deprecated
+  public final void addViolation(Violation violation) {
+    addViolation(violation, false);
+  }
+
   /**
    * Warning: the resource is automatically indexed for backward-compatibility, but it should be explictly
    * indexed before. Next versions will deactivate this automatic indexation.
index 7bfdea2c835c3842fb1fbc0e89f220a17f804580..7690bd56c942419f3a857dd736965ff6d8acb2ae 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.api.batch.rule;
 
 import org.sonar.api.BatchComponent;
-import org.sonar.api.batch.RequiresDB;
 import org.sonar.api.rule.RuleKey;
 
 import javax.annotation.CheckForNull;
@@ -32,10 +31,7 @@ import java.util.Collection;
  * be extended by plugins.
  *
  * @since 4.2
- * @deprecated since 5.2 no more possible to query any rule on batch side. Use {@link ActiveRules} to get active rules.
  */
-@Deprecated
-@RequiresDB
 public interface Rules extends BatchComponent {
 
   /**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/Violation.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/Violation.java
new file mode 100644 (file)
index 0000000..7f19e82
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.api.rules;
+
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.utils.log.Loggers;
+
+import java.util.Date;
+
+/**
+ * A class that represents a violation. A violation happens when a resource does not respect a defined rule.
+ * @deprecated in 3.6. Replaced by {@link org.sonar.api.issue.Issue}.
+ */
+@Deprecated
+public class Violation {
+
+  private Resource resource;
+  private Rule rule;
+  private String message;
+  private RulePriority severity;
+  private Integer lineId;
+  private Double cost;
+  private Date createdAt;
+  private boolean switchedOff = false;
+  private String checksum;
+  private boolean isNew = false;
+  private boolean isManual = false;
+  private Integer permanentId;
+  private Integer personId;
+
+  /**
+   * Creates of a violation from a rule. Will need to define the resource later on
+   *
+   * @deprecated since 2.3. Use the factory method {@link #create(ActiveRule, Resource)}
+   */
+  @Deprecated
+  public Violation(Rule rule) {
+    this.rule = rule;
+  }
+
+  /**
+   * Creates a fully qualified violation
+   *
+   * @param rule     the rule that has been violated
+   * @param resource the resource the violation should be attached to
+   * @deprecated since 2.3. Use the factory method create()
+   */
+  @Deprecated
+  public Violation(Rule rule, Resource resource) {
+    this.resource = resource;
+    this.rule = rule;
+  }
+
+  public Resource getResource() {
+    return resource;
+  }
+
+  /**
+   * Sets the resource the violation applies to
+   *
+   * @return the current object
+   */
+  public Violation setResource(Resource resource) {
+    this.resource = resource;
+    return this;
+  }
+
+  public Rule getRule() {
+    return rule;
+  }
+
+  /**
+   * Sets the rule violated
+   *
+   * @return the current object
+   */
+  public Violation setRule(Rule rule) {
+    this.rule = rule;
+    return this;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  /**
+   * Sets the violation message
+   *
+   * @return the current object
+   */
+  public Violation setMessage(String message) {
+    this.message = message;
+    return this;
+  }
+
+  /**
+   * @return line number (numeration starts from 1), or <code>null</code> if violation doesn't belong to concrete line
+   * @see #hasLineId()
+   */
+  public Integer getLineId() {
+    return lineId;
+  }
+
+  /**
+   * Sets the violation line.
+   *
+   * @param lineId line number (numeration starts from 1), or <code>null</code> if violation doesn't belong to concrete line
+   * @return the current object
+   */
+  public Violation setLineId(Integer lineId) {
+    if (lineId != null && lineId < 1) {
+      // TODO this normalization was added in 2.8, throw exception in future versions - see http://jira.codehaus.org/browse/SONAR-2386
+      Loggers.get(getClass()).warn("line must not be less than 1 - in future versions this will cause IllegalArgumentException");
+      this.lineId = null;
+    } else {
+      this.lineId = lineId;
+    }
+    return this;
+  }
+
+  /**
+   * @return <code>true<code> if violation belongs to concrete line
+   * @since 2.8
+   */
+  public boolean hasLineId() {
+    return lineId != null;
+  }
+
+  /**
+   * @since 2.5
+   */
+  public RulePriority getSeverity() {
+    return severity;
+  }
+
+  /**
+   * For internal use only.
+   *
+   * @since 2.5
+   */
+  public Violation setSeverity(RulePriority severity) {
+    this.severity = severity;
+    return this;
+  }
+
+  /**
+   * @deprecated since 2.5 use {@link #getSeverity()} instead. See http://jira.codehaus.org/browse/SONAR-1829
+   */
+  @Deprecated
+  public RulePriority getPriority() {
+    return severity;
+  }
+
+  /**
+   * For internal use only
+   *
+   * @deprecated since 2.5 use {@link #setSeverity(RulePriority)} instead. See http://jira.codehaus.org/browse/SONAR-1829
+   */
+  @Deprecated
+  public Violation setPriority(RulePriority priority) {
+    this.severity = priority;
+    return this;
+  }
+
+  /**
+   * @see #setCost(Double)
+   * @since 2.4
+   */
+  public Double getCost() {
+    return cost;
+  }
+
+  /**
+   * The cost to fix a violation can't be precisely computed without this information. Let's take the following example : a rule forbids to
+   * have methods whose complexity is greater than 10. Without this field "cost", the same violation is created with a method whose
+   * complexity is 15 and a method whose complexity is 100. If the cost to fix one point of complexity is 0.05h, then 15mn is necessary to
+   * fix the method whose complexity is 15, and 3h5mn is required to fix the method whose complexity is 100.
+   *
+   * @since 2.4
+   */
+  public Violation setCost(Double d) {
+    if (d == null || d >= 0) {
+      this.cost = d;
+      return this;
+    } else {
+      throw new IllegalArgumentException("Cost to fix violation can't be negative or NaN");
+    }
+  }
+
+  /**
+   * @since 2.5
+   */
+  public Date getCreatedAt() {
+    return createdAt;
+  }
+
+  /**
+   * For internal use only
+   *
+   * @since 2.5
+   */
+  public Violation setCreatedAt(Date createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  /**
+   * Switches off the current violation. This is a kind of "mute", which means the violation exists but won't be counted as an active
+   * violation (and thus, won't be counted in the total number of violations). It's usually used for false-positives.
+   * <p/>
+   * The extensions which call this method must be executed
+   *
+   * @param b if true, the violation is considered OFF
+   * @since 2.8
+   */
+  public Violation setSwitchedOff(boolean b) {
+    this.switchedOff = b;
+    return this;
+  }
+
+  /**
+   * Tells whether this violation is ON or OFF.
+   *
+   * @since 2.8
+   */
+  public boolean isSwitchedOff() {
+    return switchedOff;
+  }
+
+  /**
+   * Checksum is available in decorators executed after the barrier {@link org.sonar.api.batch.DecoratorBarriers#END_OF_VIOLATION_TRACKING}
+   */
+  public String getChecksum() {
+    return checksum;
+  }
+
+  /**
+   * For internal use only. Checksum is automatically set by Sonar. Plugins must not call this method.
+   */
+  public Violation setChecksum(String s) {
+    this.checksum = s;
+    return this;
+  }
+
+  /**
+   * A violation is considered as "new" if it has been created after the reference analysis
+   * (the "previous" analysis).
+   * This method must be used only by post-jobs and decorators depending on the barrier
+   * {@link org.sonar.api.batch.DecoratorBarriers#END_OF_VIOLATION_TRACKING}
+   *
+   * @since 2.9
+   */
+  public boolean isNew() {
+    return isNew;
+  }
+
+  /**
+   * For internal use only. MUST NOT BE SET FROM PLUGINS.
+   *
+   * @since 2.9
+   */
+  public Violation setNew(boolean b) {
+    isNew = b;
+    return this;
+  }
+
+  /**
+   * @since 2.13
+   */
+  public boolean isManual() {
+    return isManual;
+  }
+
+  /**
+   * For internal use only. MUST NOT BE SET FROM PLUGINS.
+   *
+   * @since 2.13
+   */
+  public Violation setManual(boolean b) {
+    isManual = b;
+    return this;
+  }
+
+  /**
+   * For internal use only. MUST NOT BE SET FROM PLUGINS.
+   *
+   * @since 2.13
+   */
+  public Integer getPermanentId() {
+    return permanentId;
+  }
+
+  /**
+   * For internal use only. MUST NOT BE SET FROM PLUGINS.
+   *
+   * @since 2.13
+   */
+  public Violation setPermanentId(Integer i) {
+    this.permanentId = i;
+    return this;
+  }
+
+  /**
+   * @since 2.13
+   */
+  public Integer getPersonId() {
+    return personId;
+  }
+
+  /**
+   * For internal use only.
+   * 
+   * @since 2.13
+   */
+  public Violation setPersonId(Integer i) {
+    this.personId = i;
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this);
+  }
+
+  public static Violation create(ActiveRule activeRule, Resource resource) {
+    return new Violation(activeRule.getRule()).setResource(resource);
+  }
+
+  public static Violation create(Rule rule, Resource resource) {
+    return new Violation(rule).setResource(resource);
+  }
+
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/rules/ViolationTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/rules/ViolationTest.java
new file mode 100644 (file)
index 0000000..029fcc9
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.api.rules;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ViolationTest {
+  private Violation violation;
+
+  @Before
+  public void setUp() {
+    violation = Violation.create((Rule) null, null);
+  }
+
+  /**
+   * See http://jira.codehaus.org/browse/SONAR-2386
+   */
+  @Test
+  public void testLineIdContract() {
+    violation.setLineId(null);
+    assertThat(violation.hasLineId(), is(false));
+    assertThat(violation.getLineId(), nullValue());
+
+    violation.setLineId(0);
+    assertThat(violation.hasLineId(), is(false));
+    assertThat(violation.getLineId(), nullValue());
+
+    violation.setLineId(1);
+    assertThat(violation.hasLineId(), is(true));
+    assertThat(violation.getLineId(), is(1));
+  }
+
+  @Test
+  public void testCostContract() {
+    violation.setCost(null);
+    assertThat(violation.getCost(), nullValue());
+
+    violation.setCost(1.0);
+    assertThat(violation.getCost(), is(1.0));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCostContract_NaN() {
+    violation.setCost(Double.NaN);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCostContract_Negative() {
+    violation.setCost(-1.0);
+  }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/test/IsViolation.java b/sonar-plugin-api/src/test/java/org/sonar/api/test/IsViolation.java
new file mode 100644 (file)
index 0000000..c8b9a6b
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.api.test;
+
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+
+public class IsViolation extends ArgumentMatcher<Violation> {
+
+  private Rule rule;
+  private String message;
+  private Resource resource;
+  private Integer lineId;
+
+  public IsViolation(Violation wanted) {
+    this.lineId = wanted.getLineId();
+    this.message = wanted.getMessage();
+    this.resource = wanted.getResource();
+    this.rule = wanted.getRule();
+  }
+
+  public IsViolation(Rule rule, String message, Resource resource, Integer lineId) {
+    this.rule = rule;
+    this.message = message;
+    this.resource = resource;
+    this.lineId = lineId;
+  }
+
+  @Override
+  public boolean matches(Object o) {
+    Violation violation = (Violation) o;
+    if (lineId != null && !lineId.equals(violation.getLineId())) {
+      return false;
+    }
+
+    if (message != null && !message.equals(violation.getMessage())) {
+      return false;
+    }
+
+    if (resource != null && !resource.equals(violation.getResource())) {
+      return false;
+    }
+
+    if (rule != null && !rule.equals(violation.getRule())) {
+      return false;
+    }
+
+    return true;
+  }
+}