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.

Checks.java 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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.api.batch.rule;
  21. import java.lang.reflect.Field;
  22. import java.util.Arrays;
  23. import java.util.Collection;
  24. import java.util.HashMap;
  25. import java.util.IdentityHashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import javax.annotation.CheckForNull;
  29. import org.apache.commons.lang.StringUtils;
  30. import org.sonar.api.rule.RuleKey;
  31. import org.sonar.api.utils.AnnotationUtils;
  32. import org.sonar.api.utils.FieldUtils2;
  33. import org.sonar.api.utils.SonarException;
  34. import org.sonar.check.RuleProperty;
  35. /**
  36. * Instantiates checks (objects that provide implementation of coding
  37. * rules) that use sonar-check-api annotations. Checks are selected and configured
  38. * from the Quality profiles enabled on the current module.
  39. * <br>
  40. * Example of check class:
  41. * <pre>
  42. * {@literal @}org.sonar.check.Rule(key = "S001")
  43. * public class CheckS001 {
  44. * {@literal @}org.sonar.check.RuleProperty
  45. * private String pattern;
  46. *
  47. * public String getPattern() {
  48. * return pattern;
  49. * }
  50. * }
  51. * </pre>
  52. * How to use:
  53. * <pre>
  54. * public class MyRuleEngine extends BatchExtension {
  55. * private final CheckFactory checkFactory;
  56. *
  57. * public MyRuleEngine(CheckFactory checkFactory) {
  58. * this.checkFactory = checkFactory;
  59. * }
  60. *
  61. * public void execute() {
  62. * Checks checks = checkFactory.create("my-rule-repository");
  63. * checks.addAnnotatedChecks(CheckS001.class);
  64. * // checks.all() contains an instance of CheckS001
  65. * // with field "pattern" set to the value specified in
  66. * // the Quality profile
  67. *
  68. * // Checks are used to detect issues on source code
  69. *
  70. * // checks.ruleKey(obj) can be used to create the related issues
  71. * }
  72. * }
  73. * </pre>
  74. * <br>
  75. * It replaces org.sonar.api.checks.AnnotationCheckFactory
  76. *
  77. * @since 4.2
  78. */
  79. public class Checks<C> {
  80. private final ActiveRules activeRules;
  81. private final String repository;
  82. private final Map<RuleKey, C> checkByRule = new HashMap<>();
  83. private final Map<C, RuleKey> ruleByCheck = new IdentityHashMap<>();
  84. Checks(ActiveRules activeRules, String repository) {
  85. this.activeRules = activeRules;
  86. this.repository = repository;
  87. }
  88. @CheckForNull
  89. public C of(RuleKey ruleKey) {
  90. return checkByRule.get(ruleKey);
  91. }
  92. public Collection<C> all() {
  93. return checkByRule.values();
  94. }
  95. @CheckForNull
  96. public RuleKey ruleKey(C check) {
  97. return ruleByCheck.get(check);
  98. }
  99. private void add(RuleKey ruleKey, C obj) {
  100. checkByRule.put(ruleKey, obj);
  101. ruleByCheck.put(obj, ruleKey);
  102. }
  103. public Checks<C> addAnnotatedChecks(Object... checkClassesOrObjects) {
  104. return addAnnotatedChecks((Iterable) Arrays.asList(checkClassesOrObjects));
  105. }
  106. public Checks<C> addAnnotatedChecks(Iterable checkClassesOrObjects) {
  107. Map<String, Object> checksByEngineKey = new HashMap<>();
  108. for (Object checkClassesOrObject : checkClassesOrObjects) {
  109. String engineKey = annotatedEngineKey(checkClassesOrObject);
  110. if (engineKey != null) {
  111. checksByEngineKey.put(engineKey, checkClassesOrObject);
  112. }
  113. }
  114. for (ActiveRule activeRule : activeRules.findByRepository(repository)) {
  115. String engineKey = StringUtils.defaultIfBlank(activeRule.templateRuleKey(), activeRule.ruleKey().rule());
  116. Object checkClassesOrObject = checksByEngineKey.get(engineKey);
  117. if (checkClassesOrObject != null) {
  118. Object obj = instantiate(activeRule, checkClassesOrObject);
  119. add(activeRule.ruleKey(), (C) obj);
  120. }
  121. }
  122. return this;
  123. }
  124. private static String annotatedEngineKey(Object annotatedClassOrObject) {
  125. String key = null;
  126. org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(annotatedClassOrObject, org.sonar.check.Rule.class);
  127. if (ruleAnnotation != null) {
  128. key = ruleAnnotation.key();
  129. }
  130. Class clazz = annotatedClassOrObject.getClass();
  131. if (annotatedClassOrObject instanceof Class) {
  132. clazz = (Class) annotatedClassOrObject;
  133. }
  134. return StringUtils.defaultIfEmpty(key, clazz.getCanonicalName());
  135. }
  136. private static Object instantiate(ActiveRule activeRule, Object checkClassOrInstance) {
  137. try {
  138. Object check = checkClassOrInstance;
  139. if (check instanceof Class) {
  140. check = ((Class) checkClassOrInstance).newInstance();
  141. }
  142. configureFields(activeRule, check);
  143. return check;
  144. } catch (InstantiationException | IllegalAccessException e) {
  145. throw failToInstantiateCheck(activeRule, checkClassOrInstance, e);
  146. }
  147. }
  148. private static RuntimeException failToInstantiateCheck(ActiveRule activeRule, Object checkClassOrInstance, Exception e) {
  149. throw new IllegalStateException(String.format("Fail to instantiate class %s for rule %s", checkClassOrInstance, activeRule.ruleKey()), e);
  150. }
  151. private static void configureFields(ActiveRule activeRule, Object check) {
  152. for (Map.Entry<String, String> param : activeRule.params().entrySet()) {
  153. Field field = getField(check, param.getKey());
  154. if (field == null) {
  155. throw new IllegalStateException(
  156. String.format("The field '%s' does not exist or is not annotated with @RuleProperty in the class %s", param.getKey(), check.getClass().getName()));
  157. }
  158. if (StringUtils.isNotBlank(param.getValue())) {
  159. configureField(check, field, param.getValue());
  160. }
  161. }
  162. }
  163. @CheckForNull
  164. private static Field getField(Object check, String key) {
  165. List<Field> fields = FieldUtils2.getFields(check.getClass(), true);
  166. for (Field field : fields) {
  167. RuleProperty propertyAnnotation = field.getAnnotation(RuleProperty.class);
  168. if (propertyAnnotation != null && (StringUtils.equals(key, field.getName()) || StringUtils.equals(key, propertyAnnotation.key()))) {
  169. return field;
  170. }
  171. }
  172. return null;
  173. }
  174. private static void configureField(Object check, Field field, String value) {
  175. try {
  176. field.setAccessible(true);
  177. if (field.getType().equals(String.class)) {
  178. field.set(check, value);
  179. } else if (int.class == field.getType()) {
  180. field.setInt(check, Integer.parseInt(value));
  181. } else if (short.class == field.getType()) {
  182. field.setShort(check, Short.parseShort(value));
  183. } else if (long.class == field.getType()) {
  184. field.setLong(check, Long.parseLong(value));
  185. } else if (double.class == field.getType()) {
  186. field.setDouble(check, Double.parseDouble(value));
  187. } else if (boolean.class == field.getType()) {
  188. field.setBoolean(check, Boolean.parseBoolean(value));
  189. } else if (byte.class == field.getType()) {
  190. field.setByte(check, Byte.parseByte(value));
  191. } else if (Integer.class == field.getType()) {
  192. field.set(check, Integer.parseInt(value));
  193. } else if (Long.class == field.getType()) {
  194. field.set(check, Long.parseLong(value));
  195. } else if (Double.class == field.getType()) {
  196. field.set(check, Double.parseDouble(value));
  197. } else if (Boolean.class == field.getType()) {
  198. field.set(check, Boolean.parseBoolean(value));
  199. } else {
  200. throw new SonarException("The type of the field " + field + " is not supported: " + field.getType());
  201. }
  202. } catch (IllegalAccessException e) {
  203. throw new SonarException("Can not set the value of the field " + field + " in the class: " + check.getClass().getName(), e);
  204. }
  205. }
  206. }