]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2505 support tracking of violations on dry runs
authorSimon Brandhof <simon.brandhof@gmail.com>
Fri, 17 Jun 2011 16:01:48 +0000 (18:01 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Fri, 17 Jun 2011 16:01:57 +0000 (18:01 +0200)
29 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/SourceChecksum.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationPersisterDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/SourceChecksumTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shared.xml [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchModule.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ProjectModule.java
sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java
sonar-batch/src/main/java/org/sonar/batch/index/PersistenceManager.java
sonar-batch/src/main/java/org/sonar/batch/index/ReadOnlyPersistenceManager.java
sonar-batch/src/main/java/org/sonar/batch/index/SourcePersister.java
sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldCopyPermanentIdFromPastViolation-result.xml [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldCopySwitchedOffFromPastViolation-result.xml [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldInsertViolations-result.xml [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/batch/SonarIndex.java
sonar-plugin-api/src/main/java/org/sonar/api/rules/Violation.java

index 08817c0ac0bdd47acd5f5e02cce389da67b84cb0..c14be8d33108de2609eab1048f41c5fbdcde0428 100644 (file)
@@ -214,10 +214,12 @@ public class CorePlugin extends SonarPlugin {
     extensions.add(DirectoriesDecorator.class);
     extensions.add(FilesDecorator.class);
     extensions.add(CloseReviewsDecorator.class);
+    extensions.add(ReferenceAnalysis.class);
 
     // time machine
     extensions.add(TendencyDecorator.class);
     extensions.add(VariationDecorator.class);
+    extensions.add(ViolationTrackingDecorator.class);
     extensions.add(ViolationPersisterDecorator.class);
     extensions.add(NewViolationsDecorator.class);
     extensions.add(TimeMachineConfigurationPersister.class);
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java
new file mode 100644 (file)
index 0000000..4a04c1e
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Resource;
+
+import javax.persistence.Query;
+import java.util.Collections;
+import java.util.List;
+
+public class ReferenceAnalysis implements BatchExtension {
+
+  private DatabaseSession session;
+
+  public ReferenceAnalysis(DatabaseSession session) {
+    this.session = session;
+  }
+
+  public List<RuleFailureModel> getViolations(Resource resource) {
+    Snapshot snapshot = getSnapshot(resource);
+    if (snapshot != null) {
+      return session.getResults(RuleFailureModel.class, "snapshotId", snapshot.getId());
+    }
+    return Collections.emptyList();
+  }
+
+  Snapshot getSnapshot(Resource resource) {
+    Query query = session.createQuery("from " + Snapshot.class.getSimpleName() + " s where s.last=true and s.resourceId=(select r.id from "
+        + ResourceModel.class.getSimpleName() + " r where r.key=:key)");
+    query.setParameter("key", resource.getEffectiveKey());
+    return session.getSingleResult(query, null);
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/SourceChecksum.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/SourceChecksum.java
new file mode 100644 (file)
index 0000000..8d6c327
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.List;
+
+public final class SourceChecksum {
+
+  private static final String SPACE_CHARS = "\t\n\r ";
+
+  private SourceChecksum() {
+    // only static methods
+  }
+
+  public static List<String> lineChecksumsOfFile(String file) {
+    List<String> result = Lists.newArrayList();
+    if (file != null) {
+      String[] lines = file.split("\r?\n|\r", -1);
+      for (String line : lines) {
+        result.add(lineChecksum(line));
+      }
+    }
+    return result;
+  }
+
+  public static String lineChecksum(String line) {
+    String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
+    return DigestUtils.md5Hex(reducedLine);
+  }
+
+}
index b14e76496701e9ba0c3a9ddf056eaeaeefc394b4..43f7887ca33a9ca9b97a30a1bb47e5c62cb2c1d3 100644 (file)
  */
 package org.sonar.plugins.core.timemachine;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang.ObjectUtils;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.Decorator;
-import org.sonar.api.batch.DecoratorBarriers;
-import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.batch.DependedUpon;
-import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.*;
+import org.sonar.api.database.DatabaseSession;
 import org.sonar.api.database.model.RuleFailureModel;
-import org.sonar.api.database.model.SnapshotSource;
+import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.Violation;
-import org.sonar.batch.components.PastViolationsLoader;
-import org.sonar.batch.index.ViolationPersister;
-
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
+import org.sonar.batch.index.ResourcePersister;
 import org.sonar.core.NotDryRun;
 
+import java.util.List;
+
 @NotDryRun
-@DependsUpon({ DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING })
 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
 public class ViolationPersisterDecorator implements Decorator {
 
-  /**
-   * Those chars would be ignored during generation of checksums.
-   */
-  private static final String SPACE_CHARS = "\t\n\r ";
-
-  private PastViolationsLoader pastViolationsLoader;
-  private ViolationPersister violationPersister;
+  private ViolationTrackingDecorator tracker;
+  private ResourcePersister persister;
+  private RuleFinder ruleFinder;
+  private DatabaseSession session;
 
-  List<String> checksums;
-
-  public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
-    this.pastViolationsLoader = pastViolationsLoader;
-    this.violationPersister = violationPersister;
+  public ViolationPersisterDecorator(ViolationTrackingDecorator tracker, ResourcePersister persister, RuleFinder ruleFinder, DatabaseSession session) {
+    this.tracker = tracker;
+    this.persister = persister;
+    this.ruleFinder = ruleFinder;
+    this.session = session;
   }
 
   public boolean shouldExecuteOnProject(Project project) {
     return true;
   }
 
-  public void decorate(Resource resource, DecoratorContext context) {
-    if (context.getViolations().isEmpty()) {
-      return;
-    }
-    // Load new violations
-    List<Violation> newViolations = context.getViolations();
-
-    // Load past violations
-    List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
-
-    // Load current source code and calculate checksums for each line
-    checksums = getChecksums(pastViolationsLoader.getSource(resource));
-
-    // Map new violations with old ones
-    Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
-
-    for (Violation newViolation : newViolations) {
-      String checksum = getChecksumForLine(checksums, newViolation.getLineId());
-      violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
-    }
-    violationPersister.commit();
-    // Clear cache
-    checksums.clear();
-  }
-
-  Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
-    Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
-
-    Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
-    for (RuleFailureModel pastViolation : pastViolations) {
-      pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
-    }
-
-    // Try first to match violations on same rule with same line and with same checkum (but not necessarily with same message)
-    for (Violation newViolation : newViolations) {
-      mapViolation(newViolation,
-          findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
-          pastViolationsByRule, violationMap);
-    }
-
-    // If each new violation matches an old one we can stop the matching mechanism
-    if (violationMap.size() != newViolations.size()) {
-
-      // Try then to match violations on same rule with same message and with same checkum
-      for (Violation newViolation : newViolations) {
-        if (isNotAlreadyMapped(newViolation, violationMap)) {
-          mapViolation(newViolation,
-              findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
-              pastViolationsByRule, violationMap);
-        }
-      }
-
-      // Try then to match violations on same rule with same line and with same message
-      for (Violation newViolation : newViolations) {
-        if (isNotAlreadyMapped(newViolation, violationMap)) {
-          mapViolation(newViolation,
-              findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
-              pastViolationsByRule, violationMap);
-        }
-      }
-    }
-
-    return violationMap;
-  }
-
-  private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
-    return violationMap.get(newViolation) == null;
-  }
-
-  private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
-    for (RuleFailureModel pastViolation : pastViolations) {
-      if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
-        return pastViolation;
-      }
-    }
-    return null;
-  }
-
-  private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
-    for (RuleFailureModel pastViolation : pastViolations) {
-      if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
-        return pastViolation;
-      }
-    }
-    return null;
+  @DependsUpon
+  public Class dependsOnTracker() {
+    return ViolationTrackingDecorator.class;
   }
 
-  private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
-    for (RuleFailureModel pastViolation : pastViolations) {
-      if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) {
-        return pastViolation;
-      }
-    }
-    return null;
-  }
-
-  private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
-    return pastViolation.getChecksum() != null
-        && StringUtils.equals(pastViolation.getChecksum(), getChecksumForLine(checksums, newViolation.getLineId()));
+  public void decorate(Resource resource, DecoratorContext context) {
+    saveViolations(context.getProject(), context.getViolations());
   }
 
-  private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
-    if (pastViolation.getLine() == null && newViolation.getLineId() == null) {
-      return true;
+  void saveViolations(Project project, List<Violation> violations) {
+    for (Violation violation : violations) {
+      RuleFailureModel referenceViolation = tracker.getReferenceViolation(violation);
+      save(project, violation, referenceViolation);
     }
-    return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId());
+    session.commit();
   }
 
-  private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
-    return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
-  }
+  public void save(Project project, Violation violation, RuleFailureModel referenceViolation) {
+    Snapshot snapshot = persister.saveResource(project, violation.getResource());
 
-  private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
-      Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
-    if (pastViolation != null) {
-      pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
-      violationMap.put(newViolation, pastViolation);
+    RuleFailureModel model = createModel(violation);
+    if (referenceViolation != null) {
+      model.setPermanentId(referenceViolation.getPermanentId());
     }
-  }
-
-  /**
-   * @return checksums, never null
-   */
-  private List<String> getChecksums(SnapshotSource source) {
-    return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
-  }
+    model.setSnapshotId(snapshot.getId());
+    session.saveWithoutFlush(model);
 
-  /**
-   * @param data
-   *          can't be null
-   */
-  static List<String> getChecksums(String data) {
-    String[] lines = data.split("\r?\n|\r", -1);
-    List<String> result = Lists.newArrayList();
-    for (String line : lines) {
-      result.add(getChecksum(line));
+    if (model.getPermanentId() == null) {
+      model.setPermanentId(model.getId());
+      session.saveWithoutFlush(model);
     }
-    return result;
+    violation.setMessage(model.getMessage());// the message can be changed in the class RuleFailure (truncate + trim)
   }
 
-  static String getChecksum(String line) {
-    String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
-    return DigestUtils.md5Hex(reducedLine);
-  }
 
-  /**
-   * @return checksum or null if checksum not exists for line
-   */
-  private String getChecksumForLine(List<String> checksums, Integer line) {
-    if (line == null || line < 1 || line > checksums.size()) {
-      return null;
-    }
-    return checksums.get(line - 1);
+  private RuleFailureModel createModel(Violation violation) {
+    RuleFailureModel model = new RuleFailureModel();
+    Rule rule = ruleFinder.findByKey(violation.getRule().getRepositoryKey(), violation.getRule().getKey());
+    model.setRuleId(rule.getId());
+    model.setPriority(violation.getSeverity());
+    model.setLine(violation.getLineId());
+    model.setMessage(violation.getMessage());
+    model.setCost(violation.getCost());
+    model.setChecksum(violation.getChecksum());
+    model.setCreatedAt(violation.getCreatedAt());
+    model.setSwitchedOff(violation.isSwitchedOff());
+    return model;
   }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName();
-  }
-
 }
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java
new file mode 100644 (file)
index 0000000..bab5ee4
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.*;
+import org.sonar.api.database.model.RuleFailureModel;
+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.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+@DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
+@DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
+public class ViolationTrackingDecorator implements Decorator {
+  private ReferenceAnalysis referenceAnalysis;
+  private Map<Violation, RuleFailureModel> referenceViolationsMap = Maps.newIdentityHashMap();
+  private SonarIndex index;
+  private Project project;
+
+  public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) {
+    this.referenceAnalysis = referenceAnalysis;
+    this.index = index;
+    this.project = project;
+  }
+
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  public void decorate(Resource resource, DecoratorContext context) {
+    referenceViolationsMap.clear();
+
+    if (!context.getViolations().isEmpty()) {
+      // Load new violations
+      List<Violation> newViolations = prepareNewViolations(context);
+
+      // Load reference violations
+      List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource);
+
+      // Map new violations with old ones
+      mapViolations(newViolations, referenceViolations);
+    }
+  }
+
+  private List<Violation> prepareNewViolations(DecoratorContext context) {
+    List<Violation> result = Lists.newArrayList();
+    List<String> checksums = SourceChecksum.lineChecksumsOfFile(index.getSource(context.getResource()));
+    for (Violation violation : context.getViolations()) {
+      violation.setChecksum(getChecksumForLine(checksums, violation.getLineId()));
+      result.add(violation);
+    }
+    return result;
+  }
+
+  RuleFailureModel getReferenceViolation(Violation violation) {
+    return referenceViolationsMap.get(violation);
+  }
+
+  Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
+    Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
+    for (RuleFailureModel pastViolation : pastViolations) {
+      pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
+    }
+
+    // Try first to match violations on same rule with same line and with same checkum (but not necessarily with same message)
+    for (Violation newViolation : newViolations) {
+      mapViolation(newViolation,
+          findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
+          pastViolationsByRule, referenceViolationsMap);
+    }
+
+    // If each new violation matches an old one we can stop the matching mechanism
+    if (referenceViolationsMap.size() != newViolations.size()) {
+      // Try then to match violations on same rule with same message and with same checkum
+      for (Violation newViolation : newViolations) {
+        if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
+          mapViolation(newViolation,
+              findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
+              pastViolationsByRule, referenceViolationsMap);
+        }
+      }
+
+      // Try then to match violations on same rule with same line and with same message
+      for (Violation newViolation : newViolations) {
+        if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
+          mapViolation(newViolation,
+              findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
+              pastViolationsByRule, referenceViolationsMap);
+        }
+      }
+    }
+    return referenceViolationsMap;
+  }
+
+  private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
+    return !violationMap.containsKey(newViolation);
+  }
+
+  private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
+    for (RuleFailureModel pastViolation : pastViolations) {
+      if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
+        return pastViolation;
+      }
+    }
+    return null;
+  }
+
+  private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
+    for (RuleFailureModel pastViolation : pastViolations) {
+      if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
+        return pastViolation;
+      }
+    }
+    return null;
+  }
+
+  private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
+    for (RuleFailureModel pastViolation : pastViolations) {
+      if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) {
+        return pastViolation;
+      }
+    }
+    return null;
+  }
+
+  private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
+    return pastViolation.getChecksum() != null
+        && StringUtils.equals(pastViolation.getChecksum(), newViolation.getChecksum());
+  }
+
+  private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
+    if (pastViolation.getLine() == null && newViolation.getLineId() == null) {
+      return true;
+    }
+    return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId());
+  }
+
+  private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
+    return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
+  }
+
+  private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
+                            Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
+    if (pastViolation != null) {
+      newViolation.setCreatedAt(pastViolation.getCreatedAt());
+      newViolation.setSwitchedOff(pastViolation.isSwitchedOff());
+      pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
+      violationMap.put(newViolation, pastViolation);
+    } else {
+      newViolation.setCreatedAt(project.getAnalysisDate());
+    }
+  }
+
+  /**
+   * @return checksum or null if checksum not exists for line
+   */
+  private String getChecksumForLine(List<String> checksums, Integer line) {
+    if (line == null || line < 1 || line > checksums.size()) {
+      return null;
+    }
+    return checksums.get(line - 1);
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/SourceChecksumTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/SourceChecksumTest.java
new file mode 100644 (file)
index 0000000..3c361aa
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+public class SourceChecksumTest {
+  /**
+   * See http://jira.codehaus.org/browse/SONAR-2358
+   */
+  @Test
+  public void shouldGenerateCorrectChecksums() {
+    List<String> encoding = SourceChecksum.lineChecksumsOfFile("Привет ÐœÐ¸Ñ€");
+    assertThat(encoding.size(), is(1));
+    assertThat(encoding.get(0), is("5ba3a45e1299ede07f56e5531351be52"));
+  }
+
+  @Test
+  public void shouldSplitLinesAndIgnoreSpaces() {
+    List<String> crlf = SourceChecksum.lineChecksumsOfFile("Hello\r\nWorld");
+    List<String> lf = SourceChecksum.lineChecksumsOfFile("Hello\nWorld");
+    List<String> cr = SourceChecksum.lineChecksumsOfFile("Hello\rWorld");
+    assertThat(crlf.size(), is(2));
+    assertThat(crlf.get(0), not(equalTo(crlf.get(1))));
+    assertThat(lf, equalTo(crlf));
+    assertThat(cr, equalTo(crlf));
+
+    assertThat(SourceChecksum.lineChecksum("\tvoid  method()  {\n"),
+        equalTo(SourceChecksum.lineChecksum("  void method() {")));
+  }
+}
index 96702000de6e21d0e6ae7cd140ef172143b2eb56..ae996a82178e031d927ccb9efd84ee0013678e8e 100644 (file)
  */
 package org.sonar.plugins.core.timemachine;
 
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
-
-import java.util.List;
-import java.util.Map;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Project;
 import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RulePriority;
 import org.sonar.api.rules.Violation;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.batch.index.ResourcePersister;
+import org.sonar.core.components.DefaultRuleFinder;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.util.Arrays;
 
-import com.google.common.collect.Lists;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
-public class ViolationPersisterDecoratorTest {
+public class ViolationPersisterDecoratorTest extends AbstractDbUnitTestCase {
 
   private ViolationPersisterDecorator decorator;
+  private Rule rule1 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck", "Check Header");
+  private Rule rule2 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck", "Equals Avoid Null");
+  private JavaFile javaFile = new JavaFile("org.foo.Bar");
+  Project project = new Project("project");
+  private ViolationTrackingDecorator tracker;
 
   @Before
-  public void setUp() {
-    decorator = new ViolationPersisterDecorator(null, null);
-  }
-
-  /**
-   * See http://jira.codehaus.org/browse/SONAR-2358
-   */
-  @Test
-  public void shouldGenerateCorrectChecksums() {
-    List<String> encoding = ViolationPersisterDecorator.getChecksums("Привет ÐœÐ¸Ñ€");
-    assertThat(encoding.size(), is(1));
-    assertThat(encoding.get(0), is("5ba3a45e1299ede07f56e5531351be52"));
-  }
-
-  @Test
-  public void shouldSplitLinesAndIgnoreSpaces() {
-    List<String> crlf = ViolationPersisterDecorator.getChecksums("Hello\r\nWorld");
-    List<String> lf = ViolationPersisterDecorator.getChecksums("Hello\nWorld");
-    List<String> cr = ViolationPersisterDecorator.getChecksums("Hello\rWorld");
-    assertThat(crlf.size(), is(2));
-    assertThat(crlf.get(0), not(equalTo(crlf.get(1))));
-    assertThat(lf, equalTo(crlf));
-    assertThat(cr, equalTo(crlf));
-
-    assertThat(ViolationPersisterDecorator.getChecksum("\tvoid  method()  {\n"),
-        equalTo(ViolationPersisterDecorator.getChecksum("  void method() {")));
-  }
-
-  @Test
-  public void checksumShouldHaveGreaterPriorityThanLine() {
-    RuleFailureModel pastViolation1 = newPastViolation("message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation2 = newPastViolation("message", 3, 50, "checksum2");
-
-    Violation newViolation1 = newViolation("message", 3, 50, "checksum1");
-    Violation newViolation2 = newViolation("message", 5, 50, "checksum2");
-
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation1, newViolation2),
-        Lists.newArrayList(pastViolation1, pastViolation2));
-    assertThat(mapping.get(newViolation1), equalTo(pastViolation1));
-    assertThat(mapping.get(newViolation2), equalTo(pastViolation2));
-  }
-  
-  @Test
-  public void sameRuleAndLineAndChecksumButDifferentMessages() {
-    Violation newViolation = newViolation("new message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation = newPastViolation("old message", 1, 50, "checksum1");
-
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), equalTo(pastViolation));
+  public void before() {
+    setupData("shared");
+    Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
+    ResourcePersister resourcePersister = mock(ResourcePersister.class);
+    when(resourcePersister.saveResource((Project) anyObject(), eq(javaFile))).thenReturn(snapshot);
+    when(resourcePersister.getSnapshot(javaFile)).thenReturn(snapshot);
+    tracker = mock(ViolationTrackingDecorator.class);
+    decorator = new ViolationPersisterDecorator(tracker, resourcePersister, new DefaultRuleFinder(getSessionFactory()), getSession());
   }
 
   @Test
-  public void sameRuleAndLineMessage() {
-    Violation newViolation = newViolation("message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation = newPastViolation("message", 1, 50, "checksum2");
-
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), equalTo(pastViolation));
+  public void shouldSaveViolations() {
+    Violation violation1a = Violation.create(rule1, javaFile)
+        .setSeverity(RulePriority.CRITICAL).setLineId(20).setCost(55.6).setMessage("the message")
+        .setChecksum("checksum").setCreatedAt(DateUtils.parseDate("2010-12-25"));
+    Violation violation1b = Violation.create(rule1, javaFile)
+        .setSeverity(RulePriority.CRITICAL).setLineId(50).setCost(80.0);
+    Violation violation2 = Violation.create(rule2, javaFile)
+        .setSeverity(RulePriority.MINOR).setSwitchedOff(true);
+
+    decorator.saveViolations(project, Arrays.asList(violation1a, violation1b, violation2));
+
+    checkTables("shouldSaveViolations", "rule_failures");
   }
 
   @Test
-  public void pastMeasureHasNoChecksum() {
-    Violation newViolation = newViolation("message", 1, 50, null);
-    RuleFailureModel pastViolation = newPastViolation("message", 1, 51, null);
+  public void shouldCopyPermanentIdFromReferenceViolation() {
+    RuleFailureModel referenceViolation = getSession().getSingleResult(RuleFailureModel.class, "id", 1);
+    Violation violation = Violation.create(rule1, javaFile).setSeverity(RulePriority.MAJOR).setMessage("new message");
+    when(tracker.getReferenceViolation(violation)).thenReturn(referenceViolation);
 
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), is(nullValue()));
-  }
-
-  @Test
-  public void sameRuleAndMessageAndChecksumButDifferentLine() {
-    Violation newViolation = newViolation("message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation = newPastViolation("message", 2, 50, "checksum1");
+    decorator.saveViolations(project, Arrays.asList(violation));
 
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), equalTo(pastViolation));
+    checkTables("shouldCopyPermanentIdFromReferenceViolation", "rule_failures");
   }
-
-  @Test
-  public void shouldCreateNewViolationWhenSameRuleSameMessageButDifferentLineAndChecksum() {
-    Violation newViolation = newViolation("message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation = newPastViolation("message", 2, 50, "checksum2");
-
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), is(nullValue()));
-  }
-
-  @Test
-  public void shouldNotTrackViolationIfDifferentRule() {
-    Violation newViolation = newViolation("message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation = newPastViolation("message", 1, 51, "checksum1");
-
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), is(nullValue()));
-  }
-
-  @Test
-  public void shouldCompareViolationsWithDatabaseFormat() {
-    // violation messages are trimmed and can be abbreviated when persisted in database.
-    // Comparing violation messages must use the same format.
-    Violation newViolation = newViolation("message", 1, 50, "checksum1");
-    RuleFailureModel pastViolation = newPastViolation("       message       ", 1, 50, "checksum2");
-
-    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(pastViolation));
-    assertThat(mapping.get(newViolation), equalTo(pastViolation));
-  }
-
-  private Violation newViolation(String message, int lineId, int ruleId) {
-    Rule rule = Rule.create().setKey("rule");
-    rule.setId(ruleId);
-    return Violation.create(rule, null).setLineId(lineId).setMessage(message);
-  }
-
-  private Violation newViolation(String message, int lineId, int ruleId, String lineChecksum) {
-    Violation violation = newViolation(message, lineId, ruleId);
-    if (decorator.checksums == null) {
-      decorator.checksums = Lists.newArrayListWithExpectedSize(100);
-    }
-    for (int i = decorator.checksums.size() - 1; i < lineId; i++) {
-      decorator.checksums.add("");
-    }
-    if (lineChecksum != null) {
-      decorator.checksums.set(lineId - 1, ViolationPersisterDecorator.getChecksum(lineChecksum));
-    }
-    return violation;
-  }
-
-  private RuleFailureModel newPastViolation(String message, int lineId, int ruleId) {
-    RuleFailureModel pastViolation = new RuleFailureModel();
-    pastViolation.setId(lineId + ruleId);
-    pastViolation.setLine(lineId);
-    pastViolation.setMessage(message);
-    pastViolation.setRuleId(ruleId);
-    return pastViolation;
-  }
-
-  private RuleFailureModel newPastViolation(String message, int lineId, int ruleId, String lineChecksum) {
-    RuleFailureModel pastViolation = newPastViolation(message, lineId, ruleId);
-    if (lineChecksum != null) {
-      pastViolation.setChecksum(ViolationPersisterDecorator.getChecksum(lineChecksum));
-    }
-    return pastViolation;
-  }
-
 }
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java
new file mode 100644 (file)
index 0000000..848e990
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import com.google.common.collect.Lists;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.api.utils.DateUtils;
+
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ViolationTrackingDecoratorTest {
+
+  private ViolationTrackingDecorator decorator;
+  private Date analysisDate = DateUtils.parseDate("2010-12-25");
+
+  @Before
+  public void setUp() throws ParseException {
+    Project project = mock(Project.class);
+    when(project.getAnalysisDate()).thenReturn(analysisDate);
+    decorator = new ViolationTrackingDecorator(project, null, null);
+  }
+  
+  @Test
+  public void checksumShouldHaveGreaterPriorityThanLine() {
+    RuleFailureModel referenceViolation1 = newReferenceViolation("message", 1, 50, "checksum1");
+    RuleFailureModel referenceViolation2 = newReferenceViolation("message", 3, 50, "checksum2");
+
+    Violation newViolation1 = newViolation("message", 3, 50, "checksum1");
+    Violation newViolation2 = newViolation("message", 5, 50, "checksum2");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation1, newViolation2),
+        Lists.newArrayList(referenceViolation1, referenceViolation2));
+    assertThat(mapping.get(newViolation1), equalTo(referenceViolation1));
+    assertThat(mapping.get(newViolation2), equalTo(referenceViolation2));
+  }
+
+  @Test
+  public void sameRuleAndLineAndChecksumButDifferentMessages() {
+    Violation newViolation = newViolation("new message", 1, 50, "checksum1");
+    RuleFailureModel referenceViolation = newReferenceViolation("old message", 1, 50, "checksum1");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation));
+    assertThat(mapping.get(newViolation), equalTo(referenceViolation));
+  }
+
+  @Test
+  public void sameRuleAndLineMessage() {
+    Violation newViolation = newViolation("message", 1, 50, "checksum1");
+    RuleFailureModel refernceViolation = newReferenceViolation("message", 1, 50, "checksum2");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(refernceViolation));
+    assertThat(mapping.get(newViolation), equalTo(refernceViolation));
+  }
+
+  @Test
+  public void shouldIgnoreReferenceMeasureWithoutChecksum() {
+    Violation newViolation = newViolation("message", 1, 50, null);
+    RuleFailureModel referenceViolation = newReferenceViolation("message", 1, 51, null);
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation));
+    assertThat(mapping.get(newViolation), is(nullValue()));
+  }
+
+  @Test
+  public void sameRuleAndMessageAndChecksumButDifferentLine() {
+    Violation newViolation = newViolation("message", 1, 50, "checksum1");
+    RuleFailureModel referenceViolation = newReferenceViolation("message", 2, 50, "checksum1");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation));
+    assertThat(mapping.get(newViolation), equalTo(referenceViolation));
+  }
+
+  @Test
+  public void shouldCreateNewViolationWhenSameRuleSameMessageButDifferentLineAndChecksum() {
+    Violation newViolation = newViolation("message", 1, 50, "checksum1");
+    RuleFailureModel referenceViolation = newReferenceViolation("message", 2, 50, "checksum2");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation));
+    assertThat(mapping.get(newViolation), is(nullValue()));
+  }
+
+  @Test
+  public void shouldNotTrackViolationIfDifferentRule() {
+    Violation newViolation = newViolation("message", 1, 50, "checksum1");
+    RuleFailureModel referenceViolation = newReferenceViolation("message", 1, 51, "checksum1");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation));
+    assertThat(mapping.get(newViolation), is(nullValue()));
+  }
+
+  @Test
+  public void shouldCompareViolationsWithDatabaseFormat() {
+    // violation messages are trimmed and can be abbreviated when persisted in database.
+    // Comparing violation messages must use the same format.
+    Violation newViolation = newViolation("message", 1, 50, "checksum1");
+    RuleFailureModel referenceViolation = newReferenceViolation("       message       ", 1, 50, "checksum2");
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation));
+    assertThat(mapping.get(newViolation), equalTo(referenceViolation));
+  }
+
+  @Test
+  public void shouldSetDateOfNewViolations() {
+    Violation newViolation = newViolation("message", 1, 50, "checksum");
+    assertThat(newViolation.getCreatedAt(), nullValue());
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Collections.<RuleFailureModel>emptyList());
+    assertThat(mapping.size(), is(0));
+    assertThat(newViolation.getCreatedAt(), is(analysisDate));
+  }
+
+  @Test
+  public void shouldCopyViolationDate() {
+    Violation newViolation = newViolation("message", 1, 50, "checksum");
+    RuleFailureModel referenceViolation = newReferenceViolation("", 1, 50, "checksum");
+    Date referenceDate = DateUtils.parseDate("2009-05-18");
+    referenceViolation.setCreatedAt(referenceDate);
+    assertThat(newViolation.getCreatedAt(), nullValue());
+
+    Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.<RuleFailureModel>newArrayList(referenceViolation));
+    assertThat(mapping.size(), is(1));
+    assertThat(newViolation.getCreatedAt(), is(referenceDate));
+  }
+
+  private Violation newViolation(String message, int lineId, int ruleId) {
+    Rule rule = Rule.create().setKey("rule");
+    rule.setId(ruleId);
+    return Violation.create(rule, null).setLineId(lineId).setMessage(message);
+  }
+
+  private Violation newViolation(String message, int lineId, int ruleId, String lineChecksum) {
+    return newViolation(message, lineId, ruleId).setChecksum(lineChecksum);
+  }
+
+  private RuleFailureModel newReferenceViolation(String message, int lineId, int ruleId, String lineChecksum) {
+    RuleFailureModel referenceViolation = new RuleFailureModel();
+    referenceViolation.setId(lineId + ruleId);
+    referenceViolation.setLine(lineId);
+    referenceViolation.setMessage(message);
+    referenceViolation.setRuleId(ruleId);
+    referenceViolation.setChecksum(lineChecksum);
+    return referenceViolation;
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shared.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shared.xml
new file mode 100644 (file)
index 0000000..90a3f20
--- /dev/null
@@ -0,0 +1,24 @@
+<dataset>
+
+  <rules_categories id="1" name="Efficiency" description="[null]"/>
+  <rules_categories id="6" name="Usability" description="[null]"/>
+
+  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+         cardinality="SINGLE" parent_id="[null]"/>
+
+  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+         cardinality="SINGLE" parent_id="[null]"/>
+
+  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+            name="Bar" long_name="org.foo.Bar" description="[null]"
+            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
+             status="U" islast="false" depth="3" />
+
+  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
+  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
+</dataset>
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml
new file mode 100644 (file)
index 0000000..fc52aa2
--- /dev/null
@@ -0,0 +1,26 @@
+<dataset>
+
+  <rules_categories id="1" name="Efficiency" description="[null]"/>
+  <rules_categories id="6" name="Usability" description="[null]"/>
+
+  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+         cardinality="SINGLE" parent_id="[null]"/>
+
+  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+         cardinality="SINGLE" parent_id="[null]"/>
+
+  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+            name="Bar" long_name="org.foo.Bar" description="[null]"
+            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
+             status="U" islast="false" depth="3" />
+
+  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
+  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
+
+  <rule_failures switched_off="false" permanent_id="1" ID="3" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="2" MESSAGE="new message" LINE="[null]" COST="[null]" created_at="[null]" checksum="[null]"/>
+</dataset>
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml
new file mode 100644 (file)
index 0000000..726b925
--- /dev/null
@@ -0,0 +1,26 @@
+<dataset>
+  <rules_categories id="1" name="Efficiency" description="[null]"/>
+  <rules_categories id="6" name="Usability" description="[null]"/>
+
+  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+         cardinality="SINGLE" parent_id="[null]"/>
+
+  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+         cardinality="SINGLE" parent_id="[null]"/>
+
+  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+            name="Bar" long_name="org.foo.Bar" description="[null]"
+            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
+             status="U" islast="false" depth="3" />
+
+  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
+  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
+  <rule_failures switched_off="false" permanent_id="3" ID="3" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="the message" LINE="20" COST="55.6" created_at="2010-12-25 00:00:00.00" checksum="checksum"/>
+  <rule_failures switched_off="false" permanent_id="4" ID="4" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="[null]" LINE="50" COST="80" created_at="[null]" checksum="[null]"/>
+  <rule_failures switched_off="true" permanent_id="5" ID="5" SNAPSHOT_ID="1000" RULE_ID="31" FAILURE_LEVEL="1" MESSAGE="[null]" LINE="[null]" COST="[null]" created_at="[null]" checksum="[null]"/>
+</dataset>
\ No newline at end of file
index eb6c4552073ab9197ecad8b2514cd30837025682..0893155682129d7c0e7545e0a41bcd024663709d 100644 (file)
@@ -60,7 +60,6 @@ public class BatchModule extends Module {
       addComponent(MemoryOptimizer.class);
       addComponent(DefaultResourcePersister.class);
       addComponent(SourcePersister.class);
-      addComponent(ViolationPersister.class);
     }
 
     addComponent(Plugins.class);
index d603c34a1df073780680b7d70618bcec053d566e..59d3dff04f9bb9d17e1cc1a549a3fad51f11cde1 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.resources.ProjectFileSystem;
 import org.sonar.api.rules.DefaultRulesManager;
 import org.sonar.api.utils.SonarException;
 import org.sonar.batch.*;
-import org.sonar.batch.components.PastViolationsLoader;
 import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.batch.events.EventBus;
 import org.sonar.batch.index.DefaultIndex;
@@ -80,7 +79,6 @@ public class ProjectModule extends Module {
       // the Snapshot component will be removed when asynchronous measures are improved (required for AsynchronousMeasureSensor)
       addComponent(getComponent(DefaultResourcePersister.class).getSnapshot(project));
       addComponent(TimeMachineConfiguration.class);
-      addComponent(PastViolationsLoader.class);
     }
     addComponent(org.sonar.api.database.daos.MeasuresDao.class);
     addComponent(ProfilesDao.class);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java
deleted file mode 100644 (file)
index 6168ebc..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
- */
-package org.sonar.batch.components;
-
-import org.sonar.api.BatchExtension;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.database.model.RuleFailureModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.database.model.SnapshotSource;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.utils.SonarException;
-import org.sonar.batch.index.ResourcePersister;
-import org.sonar.core.NotDryRun;
-
-import java.util.Collections;
-import java.util.List;
-
-@NotDryRun
-public class PastViolationsLoader implements BatchExtension {
-
-  private DatabaseSession session;
-  private ResourcePersister resourcePersister;
-
-  public PastViolationsLoader(DatabaseSession session, ResourcePersister resourcePersister) {
-    this.session = session;
-    this.resourcePersister = resourcePersister;
-  }
-
-  public List<RuleFailureModel> getPastViolations(Resource resource) {
-    if (resource == null) {
-      return Collections.emptyList();
-    }
-
-    Snapshot snapshot = resourcePersister.getSnapshot(resource);
-    if (snapshot == null) {
-      throw new SonarException("This resource has no snapshot ???" + resource);
-    }
-    Snapshot previousLastSnapshot = resourcePersister.getLastSnapshot(snapshot, true);
-    if (previousLastSnapshot == null) {
-      return Collections.emptyList();
-    }
-    return session.getResults(RuleFailureModel.class,
-        "snapshotId", previousLastSnapshot.getId());
-  }
-
-  public SnapshotSource getSource(Resource resource) {
-    Snapshot snapshot = resourcePersister.getSnapshot(resource);
-    return session.getSingleResult(SnapshotSource.class,
-        "snapshotId", snapshot.getId());
-  }
-
-}
index 1accc7b4ea6491f226e8cdd6284b4dc20434fd54..5aeac943af956506007e9fb0c52e5f180120f6a0 100644 (file)
@@ -408,6 +408,10 @@ public class DefaultIndex extends SonarIndex {
     }
   }
 
+  public String getSource(Resource resource) {
+    return persistence.getSource(resource);
+  }
+
   /**
    * Does nothing if the resource is already registered.
    */
index f5722b7f5a2fc2ebe74b77906028a013e96f8423..35baf93c09dea08fe3a4f981cb6a93268681a4e9 100644 (file)
@@ -78,6 +78,10 @@ public final class DefaultPersistenceManager implements PersistenceManager {
     sourcePersister.saveSource(file, source);
   }
 
+  public String getSource(Resource resource) {
+    return sourcePersister.getSource(resource);
+  }
+
   public void saveMeasure(Resource resource, Measure measure) {
     if (ResourceUtils.isPersistable(resource)) {
       measurePersister.saveMeasure(resource, measure);
index 98de69eb77fa07eb87bd5c2db5f26c03f2006f3c..2203c2e47687957edb75a68d85d0a9c070050712 100644 (file)
@@ -42,6 +42,8 @@ public interface PersistenceManager {
 
   void setSource(Resource file, String source);
 
+  String getSource(Resource resource);
+
   void saveMeasure(Resource resource, Measure measure);
 
   Measure reloadMeasure(Measure measure);
index 7682a3dbb6ec45e7a1e566c0fd6e3800292ee1eb..1470031672c7a4b550bcdcde77c9f5ebb9b0222c 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.batch.index;
 
+import com.google.common.collect.Maps;
 import org.sonar.api.batch.Event;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.design.Dependency;
@@ -29,10 +30,14 @@ import org.sonar.api.resources.Resource;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 public final class ReadOnlyPersistenceManager implements PersistenceManager {
 
+  private Map<Resource, String> sources = Maps.newHashMap();
+
   public void clear() {
+    sources.clear();
   }
 
   public void setDelayedMode(boolean b) {
@@ -49,6 +54,11 @@ public final class ReadOnlyPersistenceManager implements PersistenceManager {
   }
 
   public void setSource(Resource file, String source) {
+    sources.put(file, source);
+  }
+
+  public String getSource(Resource resource) {
+    return sources.get(resource);
   }
 
   public void saveMeasure(Resource resource, Measure measure) {
index 0e8f700237c3a3bbb5a53b4f495f99f6a4cb1029..d4fdd10300cb50da6410f07fe50fc907785bc917 100644 (file)
@@ -49,6 +49,15 @@ public final class SourcePersister {
     addToCache(snapshot);
   }
 
+  public String getSource(Resource resource) {
+    SnapshotSource source = null;
+    Snapshot snapshot = resourcePersister.getSnapshot(resource);
+    if (snapshot!=null && snapshot.getId()!=null) {
+      source = session.getSingleResult(SnapshotSource.class, "snapshotId", snapshot.getId());
+    }
+    return source!=null ? source.getData() : null;
+  }
+
   private boolean isCached(Snapshot snapshot) {
     return savedSnapshotIds.contains(snapshot.getId());
   }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java
deleted file mode 100644 (file)
index 0b15823..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
- */
-package org.sonar.batch.index;
-
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.database.model.RuleFailureModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.resources.Project;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.Violation;
-
-public final class ViolationPersister {
-
-  private DatabaseSession session;
-  private ResourcePersister resourcePersister;
-  private RuleFinder ruleFinder;
-
-  public ViolationPersister(DatabaseSession session, ResourcePersister resourcePersister, RuleFinder ruleFinder) {
-    this.session = session;
-    this.resourcePersister = resourcePersister;
-    this.ruleFinder = ruleFinder;
-  }
-
-  void saveViolation(Project project, Violation violation) {
-    saveViolation(project, violation, null, null);
-  }
-
-  public void saveViolation(Project project, Violation violation, RuleFailureModel pastViolation, String checksum) {
-    Snapshot snapshot = resourcePersister.saveResource(project, violation.getResource());
-
-    RuleFailureModel model = createModel(violation);
-    if (pastViolation!=null) {
-      model.setCreatedAt(pastViolation.getCreatedAt());
-      model.setPermanentId(pastViolation.getPermanentId());
-      model.setSwitchedOff(pastViolation.isSwitchedOff());
-    } else {
-      // avoid plugins setting date
-      model.setCreatedAt(snapshot.getCreatedAt());
-    }
-    model.setSnapshotId(snapshot.getId());
-    model.setChecksum(checksum);
-    session.saveWithoutFlush(model);
-
-    if (model.getPermanentId()==null) {
-      model.setPermanentId(model.getId());
-      session.saveWithoutFlush(model);
-    }
-
-    // the following fields can have been changed
-    violation.setMessage(model.getMessage());// the message can be changed in the class RuleFailure (truncate + trim)
-    violation.setCreatedAt(model.getCreatedAt());
-    violation.setSwitchedOff(model.isSwitchedOff());
-  }
-  
-  public void commit() {
-    session.commit();
-  }
-
-  private RuleFailureModel createModel(Violation violation) {
-    RuleFailureModel model = new RuleFailureModel();
-    Rule rule = ruleFinder.findByKey(violation.getRule().getRepositoryKey(), violation.getRule().getKey());
-    model.setRuleId(rule.getId());
-    model.setPriority(violation.getSeverity());
-    model.setLine(violation.getLineId());
-    model.setMessage(violation.getMessage());
-    model.setCost(violation.getCost());
-    return model;
-  }
-
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java
deleted file mode 100644 (file)
index 965f4f8..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
- */
-package org.sonar.batch.components;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.database.model.RuleFailureModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.resources.JavaFile;
-import org.sonar.api.resources.Resource;
-import org.sonar.batch.index.ResourcePersister;
-import org.sonar.jpa.test.AbstractDbUnitTestCase;
-
-import java.util.List;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
-public class PastViolationsLoaderTest extends AbstractDbUnitTestCase {
-
-  private ResourcePersister resourcePersister;
-  private PastViolationsLoader loader;
-
-  @Before
-  public void setUp() {
-    setupData("shared");
-    resourcePersister = mock(ResourcePersister.class);
-    loader = new PastViolationsLoader(getSession(), resourcePersister);
-  }
-
-  @Test
-  public void shouldGetPastResourceViolations() {
-    Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
-    doReturn(snapshot).when(resourcePersister)
-        .getSnapshot(any(Resource.class));
-    doReturn(snapshot).when(resourcePersister)
-        .getLastSnapshot(any(Snapshot.class), anyBoolean());
-
-    List<RuleFailureModel> violations = loader.getPastViolations(new JavaFile("file"));
-
-    assertThat(violations.size(), is(2));
-  }
-
-  @Test
-  public void shouldReturnEmptyList() {
-    List<RuleFailureModel> violations = loader.getPastViolations(null);
-
-    assertThat(violations, notNullValue());
-    assertThat(violations.size(), is(0));
-  }
-
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java
deleted file mode 100644 (file)
index bb5bc1a..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
- */
-package org.sonar.batch.index;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.database.model.RuleFailureModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.resources.JavaFile;
-import org.sonar.api.resources.Project;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.rules.Violation;
-import org.sonar.core.components.DefaultRuleFinder;
-import org.sonar.jpa.test.AbstractDbUnitTestCase;
-
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ViolationPersisterTest extends AbstractDbUnitTestCase {
-
-  private ViolationPersister violationPersister;
-  private Rule rule1 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck", "Check Header");
-  private Rule rule2 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck", "Equals Avoid Null");
-  private JavaFile javaFile = new JavaFile("org.foo.Bar");
-  Project project = new Project("project");
-
-  @Before
-  public void before() {
-    setupData("shared");
-    Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
-    ResourcePersister resourcePersister = mock(ResourcePersister.class);
-    when(resourcePersister.saveResource((Project) anyObject(), eq(javaFile))).thenReturn(snapshot);
-    when(resourcePersister.getSnapshot(javaFile)).thenReturn(snapshot);
-    violationPersister = new ViolationPersister(getSession(), resourcePersister, new DefaultRuleFinder(getSessionFactory()));
-  }
-
-  @Test
-  public void shouldSaveViolations() {
-    Violation violation1a = Violation.create(rule1, javaFile)
-        .setSeverity(RulePriority.CRITICAL).setLineId(20).setCost(55.6)
-        .setMessage("the message");
-    Violation violation1b = Violation.create(rule1, javaFile)
-        .setSeverity(RulePriority.CRITICAL).setLineId(50).setCost(80.0);
-    Violation violation2 = Violation.create(rule2, javaFile)
-        .setSeverity(RulePriority.MINOR);
-
-    violationPersister.saveViolation(project, violation1a);
-    violationPersister.saveViolation(project, violation1b);
-    violationPersister.saveViolation(project, violation2);
-
-    checkTables("shouldInsertViolations", "rule_failures");
-  }
-
-  @Test
-  public void shouldCopyPermanentIdFromPastViolation() {
-    RuleFailureModel pastViolation = getSession().getSingleResult(RuleFailureModel.class, "id", 1);
-
-    Violation violation = Violation.create(rule1, javaFile).setSeverity(RulePriority.MAJOR).setMessage("new message");
-    violationPersister.saveViolation(project, violation, pastViolation, "line_checksum");
-
-    checkTables("shouldCopyPermanentIdFromPastViolation", "rule_failures");
-  }
-
-  @Test
-  public void shouldCopySwitchedOffFromPastViolation() {
-    RuleFailureModel pastViolation1 = getSession().getSingleResult(RuleFailureModel.class, "id", 1);
-    Violation violation1 = Violation.create(rule1, javaFile).setSeverity(RulePriority.MAJOR).setMessage("new message");
-    violationPersister.saveViolation(project, violation1, pastViolation1, "line_checksum");
-
-    RuleFailureModel pastViolation2 = getSession().getSingleResult(RuleFailureModel.class, "id", 2);
-    Violation violation2 = Violation.create(rule1, javaFile).setSeverity(RulePriority.MAJOR).setMessage("new message");
-    violationPersister.saveViolation(project, violation2, pastViolation2, "line_checksum");
-
-    checkTables("shouldCopySwitchedOffFromPastViolation", "rule_failures");
-  }
-}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml
deleted file mode 100644 (file)
index 9e11773..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<dataset>
-
-  <rules_categories id="1" name="Efficiency" description="[null]"/>
-  <rules_categories id="6" name="Usability" description="[null]"/>
-
-  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
-         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
-         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
-            name="Bar" long_name="org.foo.Bar" description="[null]"
-            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
-
-  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
-             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
-             status="U" islast="false" depth="3" />
-
-  <rule_failures switched_off="[null]" permanent_id="[null]" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" />
-  <rule_failures switched_off="[null]" permanent_id="[null]" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" />
-</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml
deleted file mode 100644 (file)
index 90a3f20..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<dataset>
-
-  <rules_categories id="1" name="Efficiency" description="[null]"/>
-  <rules_categories id="6" name="Usability" description="[null]"/>
-
-  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
-         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
-         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
-            name="Bar" long_name="org.foo.Bar" description="[null]"
-            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
-
-  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
-             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
-             status="U" islast="false" depth="3" />
-
-  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldCopyPermanentIdFromPastViolation-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldCopyPermanentIdFromPastViolation-result.xml
deleted file mode 100644 (file)
index 881a88d..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<dataset>
-
-  <rules_categories id="1" name="Efficiency" description="[null]"/>
-  <rules_categories id="6" name="Usability" description="[null]"/>
-
-  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
-         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
-         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
-            name="Bar" long_name="org.foo.Bar" description="[null]"
-            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
-
-  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
-             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
-             status="U" islast="false" depth="3" />
-
-  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-
-  <rule_failures switched_off="false" permanent_id="1" ID="3" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="2" MESSAGE="new message" LINE="[null]" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="line_checksum"/>
-</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldCopySwitchedOffFromPastViolation-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldCopySwitchedOffFromPastViolation-result.xml
deleted file mode 100644 (file)
index bc366b1..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<dataset>
-
-  <rules_categories id="1" name="Efficiency" description="[null]"/>
-  <rules_categories id="6" name="Usability" description="[null]"/>
-
-  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
-         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
-         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
-            name="Bar" long_name="org.foo.Bar" description="[null]"
-            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
-
-  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
-             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
-             status="U" islast="false" depth="3" />
-
-  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-
-  <rule_failures switched_off="false" permanent_id="1" ID="3" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="2" MESSAGE="new message" LINE="[null]" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="line_checksum"/>
-  <rule_failures switched_off="true" permanent_id="2" ID="4" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="2" MESSAGE="new message" LINE="[null]" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="line_checksum"/>
-</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldInsertViolations-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldInsertViolations-result.xml
deleted file mode 100644 (file)
index 3b3215f..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<dataset>
-  <rules_categories id="1" name="Efficiency" description="[null]"/>
-  <rules_categories id="6" name="Usability" description="[null]"/>
-
-  <rules id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
-         plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <rules id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
-         plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
-         cardinality="SINGLE" parent_id="[null]"/>
-
-  <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
-            name="Bar" long_name="org.foo.Bar" description="[null]"
-            enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
-
-  <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
-             scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
-             status="U" islast="false" depth="3" />
-
-  <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="true" permanent_id="2" ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="false" permanent_id="3" ID="3" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="the message" LINE="20" COST="55.6" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="false" permanent_id="4" ID="4" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="[null]" LINE="50" COST="80" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-  <rule_failures switched_off="false" permanent_id="5" ID="5" SNAPSHOT_ID="1000" RULE_ID="31" FAILURE_LEVEL="1" MESSAGE="[null]" LINE="[null]" COST="[null]" created_at="2008-11-01 13:58:00.00" checksum="[null]"/>
-</dataset>
\ No newline at end of file
index 4c82198ffdd2d2298fef69728c7af5676aae0dee..1e17e75dad1e4abaf455e24f8d52650fcd9a6078 100644 (file)
@@ -99,6 +99,11 @@ public abstract class SonarIndex implements DirectedGraphAccessor<Resource, Depe
    */
   public abstract void setSource(Resource reference, String source) throws DuplicatedSourceException;
 
+  /**
+   * @since 2.9
+   */
+  public abstract String getSource(Resource resource);
+
   public abstract Project getProject();
 
   public final Collection<Resource> getResources() {
@@ -133,8 +138,6 @@ public abstract class SonarIndex implements DirectedGraphAccessor<Resource, Depe
    * as a parameter.
    * 
    * @since 2.7
-   * @param the
-   *          resource on which violations are searched
    * @return the list of violations
    */
   public final List<Violation> getViolations(Resource resource) {
@@ -153,8 +156,6 @@ public abstract class SonarIndex implements DirectedGraphAccessor<Resource, Depe
   /**
    * Warning: the resource is automatically indexed for backward-compatibility, but it should be explictly
    * indexed before. Next versions will deactivate this automatic indexation.
-   *
-   * @throws SonarException if the metric is unknown.
    */
   public abstract Measure addMeasure(Resource resource, Measure measure);
 
index fc7ec23548468d744da1a628e9930bf69288f065..efab4077464bb0566dafb1158a0356a90344bf9f 100644 (file)
@@ -40,6 +40,7 @@ public class Violation {
   private Double cost;
   private Date createdAt;
   private boolean switchedOff=false;
+  private String checksum;
 
   /**
    * Creates of a violation from a rule. Will need to define the resource later on
@@ -238,27 +239,24 @@ public class Violation {
    * Tells whether this violation is ON or OFF.
    * 
    * @since 2.8
-   * @return true if the violation has been switched off
    */
   public boolean isSwitchedOff() {
     return switchedOff;
   }
 
-  @Override
-  public boolean equals(Object obj) {
-    if (!(obj instanceof Violation)) {
-      return false;
-    }
-    if (this == obj) {
-      return true;
-    }
-    Violation other = (Violation) obj;
-    return new EqualsBuilder().append(rule, other.getRule()).append(resource, other.getResource()).isEquals();
+  /**
+   * Checksum is available in decorators executed after the barrier {@link org.sonar.api.batch.DecoratorBarriers#END_OF_VIOLATION_TRACKING}
+   */
+  public String getChecksum() {
+    return checksum;
   }
 
-  @Override
-  public int hashCode() {
-    return new HashCodeBuilder(17, 37).append(getRule()).append(getResource()).toHashCode();
+  /**
+   * 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;
   }
 
   @Override