You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DuplicationsParser.java 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.duplication.ws;
  21. import com.google.common.annotations.VisibleForTesting;
  22. import com.google.common.base.Optional;
  23. import java.io.Serializable;
  24. import java.io.StringReader;
  25. import java.util.Collections;
  26. import java.util.Comparator;
  27. import java.util.List;
  28. import java.util.Map;
  29. import javax.annotation.CheckForNull;
  30. import javax.annotation.Nullable;
  31. import javax.xml.stream.XMLInputFactory;
  32. import javax.xml.stream.XMLStreamException;
  33. import org.apache.commons.lang.StringUtils;
  34. import org.codehaus.staxmate.SMInputFactory;
  35. import org.codehaus.staxmate.in.SMHierarchicCursor;
  36. import org.codehaus.staxmate.in.SMInputCursor;
  37. import org.sonar.api.server.ServerSide;
  38. import org.sonar.db.DbSession;
  39. import org.sonar.db.component.ComponentDao;
  40. import org.sonar.db.component.ComponentDto;
  41. import static com.google.common.collect.Lists.newArrayList;
  42. import static com.google.common.collect.Maps.newHashMap;
  43. @ServerSide
  44. public class DuplicationsParser {
  45. private final ComponentDao componentDao;
  46. public DuplicationsParser(ComponentDao componentDao) {
  47. this.componentDao = componentDao;
  48. }
  49. public List<Block> parse(DbSession session, ComponentDto component, @Nullable String branch, @Nullable String pullRequest, @Nullable String duplicationsData) {
  50. Map<String, ComponentDto> componentsByKey = newHashMap();
  51. List<Block> blocks = newArrayList();
  52. if (duplicationsData != null) {
  53. try {
  54. SMInputFactory inputFactory = initStax();
  55. SMHierarchicCursor root = inputFactory.rootElementCursor(new StringReader(duplicationsData));
  56. root.advance(); // <duplications>
  57. SMInputCursor cursor = root.childElementCursor("g");
  58. while (cursor.getNext() != null) {
  59. List<Duplication> duplications = newArrayList();
  60. SMInputCursor bCursor = cursor.childElementCursor("b");
  61. while (bCursor.getNext() != null) {
  62. String from = bCursor.getAttrValue("s");
  63. String size = bCursor.getAttrValue("l");
  64. String componentKey = bCursor.getAttrValue("r");
  65. if (from != null && size != null && componentKey != null) {
  66. duplications.add(createDuplication(componentsByKey, branch, pullRequest, from, size, componentKey, session));
  67. }
  68. }
  69. Collections.sort(duplications, new DuplicationComparator(component.uuid(), component.projectUuid()));
  70. blocks.add(new Block(duplications));
  71. }
  72. Collections.sort(blocks, new BlockComparator());
  73. } catch (XMLStreamException e) {
  74. throw new IllegalStateException("XML is not valid", e);
  75. }
  76. }
  77. return blocks;
  78. }
  79. private Duplication createDuplication(Map<String, ComponentDto> componentsByKey, @Nullable String branch, @Nullable String pullRequest, String from, String size,
  80. String componentDbKey, DbSession session) {
  81. String componentKey = convertToKey(componentDbKey);
  82. ComponentDto component = componentsByKey.get(componentKey);
  83. if (component == null) {
  84. Optional<ComponentDto> componentDtoOptional;
  85. if (branch != null) {
  86. componentDtoOptional = Optional.fromNullable(componentDao.selectByKeyAndBranch(session, componentKey, branch).orElseGet(null));
  87. } else if (pullRequest != null) {
  88. componentDtoOptional = Optional.fromNullable(componentDao.selectByKeyAndPullRequest(session, componentKey, pullRequest).orElseGet(null));
  89. } else {
  90. componentDtoOptional = componentDao.selectByKey(session, componentKey);
  91. }
  92. component = componentDtoOptional.isPresent() ? componentDtoOptional.get() : null;
  93. componentsByKey.put(componentKey, component);
  94. }
  95. return new Duplication(component, Integer.valueOf(from), Integer.valueOf(size));
  96. }
  97. private static String convertToKey(String dbKey) {
  98. return new ComponentDto().setDbKey(dbKey).getKey();
  99. }
  100. private static SMInputFactory initStax() {
  101. XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
  102. xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
  103. xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
  104. // just so it won't try to load DTD in if there's DOCTYPE
  105. xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
  106. xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
  107. return new SMInputFactory(xmlFactory);
  108. }
  109. @VisibleForTesting
  110. static class DuplicationComparator implements Comparator<Duplication>, Serializable {
  111. private static final long serialVersionUID = 1;
  112. private final String uuid;
  113. private final String projectUuid;
  114. DuplicationComparator(String uuid, String projectUuid) {
  115. this.uuid = uuid;
  116. this.projectUuid = projectUuid;
  117. }
  118. @Override
  119. public int compare(@Nullable Duplication d1, @Nullable Duplication d2) {
  120. if (d1 == null || d2 == null) {
  121. return -1;
  122. }
  123. ComponentDto file1 = d1.file();
  124. ComponentDto file2 = d2.file();
  125. if (file1 == null || file2 == null) {
  126. return -1;
  127. }
  128. if (file1.equals(d2.file())) {
  129. // if duplication on same file => order by starting line
  130. return d1.from().compareTo(d2.from());
  131. }
  132. if (file1.uuid().equals(uuid)) {
  133. // the current resource must be displayed first
  134. return -1;
  135. }
  136. if (file2.uuid().equals(uuid)) {
  137. // the current resource must be displayed first
  138. return 1;
  139. }
  140. if (StringUtils.equals(file1.projectUuid(), projectUuid) && !StringUtils.equals(file2.projectUuid(), projectUuid)) {
  141. // if resource is in the same project, this it must be displayed first
  142. return -1;
  143. }
  144. if (StringUtils.equals(file2.projectUuid(), projectUuid) && !StringUtils.equals(file1.projectUuid(), projectUuid)) {
  145. // if resource is in the same project, this it must be displayed first
  146. return 1;
  147. }
  148. return d1.from().compareTo(d2.from());
  149. }
  150. }
  151. private static class BlockComparator implements Comparator<Block>, Serializable {
  152. private static final long serialVersionUID = 1;
  153. @Override
  154. public int compare(@Nullable Block b1, @Nullable Block b2) {
  155. if (b1 == null || b2 == null) {
  156. return -1;
  157. }
  158. List<Duplication> duplications1 = b1.getDuplications();
  159. List<Duplication> duplications2 = b2.getDuplications();
  160. if (duplications1.isEmpty() || duplications2.isEmpty()) {
  161. return -1;
  162. }
  163. return duplications1.get(0).from().compareTo(duplications2.get(0).from());
  164. }
  165. }
  166. public static class Duplication {
  167. private final ComponentDto file;
  168. private final Integer from;
  169. private final Integer size;
  170. Duplication(@Nullable ComponentDto file, Integer from, Integer size) {
  171. this.file = file;
  172. this.from = from;
  173. this.size = size;
  174. }
  175. /**
  176. * File can be null when duplication is linked on a removed file
  177. */
  178. @CheckForNull
  179. ComponentDto file() {
  180. return file;
  181. }
  182. Integer from() {
  183. return from;
  184. }
  185. Integer size() {
  186. return size;
  187. }
  188. }
  189. static class Block {
  190. private final List<Duplication> duplications;
  191. public Block(List<Duplication> duplications) {
  192. this.duplications = duplications;
  193. }
  194. public List<Duplication> getDuplications() {
  195. return duplications;
  196. }
  197. }
  198. }