001/* 002 * SonarQube 003 * Copyright (C) 2009-2016 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.batch.rule; 021 022import com.google.common.collect.Maps; 023import java.lang.reflect.Field; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.IdentityHashMap; 028import java.util.List; 029import java.util.Map; 030import javax.annotation.CheckForNull; 031import org.apache.commons.lang.StringUtils; 032import org.sonar.api.rule.RuleKey; 033import org.sonar.api.utils.AnnotationUtils; 034import org.sonar.api.utils.FieldUtils2; 035import org.sonar.api.utils.SonarException; 036import org.sonar.check.RuleProperty; 037 038/** 039 * Instantiates checks (objects that provide implementation of coding 040 * rules) that use sonar-check-api annotations. Checks are selected and configured 041 * from the Quality profiles enabled on the current module. 042 * <br> 043 * Example of check class: 044 * <pre> 045 * {@literal @}org.sonar.check.Rule(key = "S001") 046 * public class CheckS001 { 047 * {@literal @}org.sonar.check.RuleProperty 048 * private String pattern; 049 * 050 * public String getPattern() { 051 * return pattern; 052 * } 053 * } 054 * </pre> 055 * How to use: 056 * <pre> 057 * public class MyRuleEngine extends BatchExtension { 058 * private final CheckFactory checkFactory; 059 * 060 * public MyRuleEngine(CheckFactory checkFactory) { 061 * this.checkFactory = checkFactory; 062 * } 063 * 064 * public void execute() { 065 * Checks checks = checkFactory.create("my-rule-repository"); 066 * checks.addAnnotatedChecks(CheckS001.class); 067 * // checks.all() contains an instance of CheckS001 068 * // with field "pattern" set to the value specified in 069 * // the Quality profile 070 * 071 * // Checks are used to detect issues on source code 072 * 073 * // checks.ruleKey(obj) can be used to create the related issues 074 * } 075 * } 076 * </pre> 077 * <br> 078 * It replaces org.sonar.api.checks.AnnotationCheckFactory 079 * 080 * @since 4.2 081 */ 082public class Checks<C> { 083 private final ActiveRules activeRules; 084 private final String repository; 085 private final Map<RuleKey, C> checkByRule = new HashMap<>(); 086 private final Map<C, RuleKey> ruleByCheck = new IdentityHashMap<>(); 087 088 Checks(ActiveRules activeRules, String repository) { 089 this.activeRules = activeRules; 090 this.repository = repository; 091 } 092 093 @CheckForNull 094 public C of(RuleKey ruleKey) { 095 return checkByRule.get(ruleKey); 096 } 097 098 public Collection<C> all() { 099 return checkByRule.values(); 100 } 101 102 @CheckForNull 103 public RuleKey ruleKey(C check) { 104 return ruleByCheck.get(check); 105 } 106 107 private void add(RuleKey ruleKey, C obj) { 108 checkByRule.put(ruleKey, obj); 109 ruleByCheck.put(obj, ruleKey); 110 } 111 112 public Checks<C> addAnnotatedChecks(Object... checkClassesOrObjects) { 113 return addAnnotatedChecks((Iterable) Arrays.asList(checkClassesOrObjects)); 114 } 115 116 /** 117 * @deprecated since 5.2 use {@link #addAnnotatedChecks(Iterable)} 118 */ 119 @Deprecated 120 public Checks<C> addAnnotatedChecks(Collection checkClassesOrObjects) { 121 return addAnnotatedChecks((Iterable) checkClassesOrObjects); 122 } 123 124 public Checks<C> addAnnotatedChecks(Iterable checkClassesOrObjects) { 125 Map<String, Object> checksByEngineKey = Maps.newHashMap(); 126 for (Object checkClassesOrObject : checkClassesOrObjects) { 127 String engineKey = annotatedEngineKey(checkClassesOrObject); 128 if (engineKey != null) { 129 checksByEngineKey.put(engineKey, checkClassesOrObject); 130 } 131 } 132 133 for (ActiveRule activeRule : activeRules.findByRepository(repository)) { 134 String engineKey = StringUtils.defaultIfBlank(activeRule.templateRuleKey(), activeRule.ruleKey().rule()); 135 Object checkClassesOrObject = checksByEngineKey.get(engineKey); 136 if (checkClassesOrObject != null) { 137 Object obj = instantiate(activeRule, checkClassesOrObject); 138 add(activeRule.ruleKey(), (C) obj); 139 } 140 } 141 return this; 142 } 143 144 private static String annotatedEngineKey(Object annotatedClassOrObject) { 145 String key = null; 146 org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(annotatedClassOrObject, org.sonar.check.Rule.class); 147 if (ruleAnnotation != null) { 148 key = ruleAnnotation.key(); 149 } 150 Class clazz = annotatedClassOrObject.getClass(); 151 if (annotatedClassOrObject instanceof Class) { 152 clazz = (Class) annotatedClassOrObject; 153 } 154 return StringUtils.defaultIfEmpty(key, clazz.getCanonicalName()); 155 } 156 157 private static Object instantiate(ActiveRule activeRule, Object checkClassOrInstance) { 158 try { 159 Object check = checkClassOrInstance; 160 if (check instanceof Class) { 161 check = ((Class) checkClassOrInstance).newInstance(); 162 } 163 configureFields(activeRule, check); 164 return check; 165 } catch (InstantiationException | IllegalAccessException e) { 166 throw failToInstantiateCheck(activeRule, checkClassOrInstance, e); 167 } 168 } 169 170 private static RuntimeException failToInstantiateCheck(ActiveRule activeRule, Object checkClassOrInstance, Exception e) { 171 throw new IllegalStateException(String.format("Fail to instantiate class %s for rule %s", checkClassOrInstance, activeRule.ruleKey()), e); 172 } 173 174 private static void configureFields(ActiveRule activeRule, Object check) { 175 for (Map.Entry<String, String> param : activeRule.params().entrySet()) { 176 Field field = getField(check, param.getKey()); 177 if (field == null) { 178 throw new IllegalStateException( 179 String.format("The field '%s' does not exist or is not annotated with @RuleProperty in the class %s", param.getKey(), check.getClass().getName())); 180 } 181 if (StringUtils.isNotBlank(param.getValue())) { 182 configureField(check, field, param.getValue()); 183 } 184 } 185 } 186 187 @CheckForNull 188 private static Field getField(Object check, String key) { 189 List<Field> fields = FieldUtils2.getFields(check.getClass(), true); 190 for (Field field : fields) { 191 RuleProperty propertyAnnotation = field.getAnnotation(RuleProperty.class); 192 if (propertyAnnotation != null && (StringUtils.equals(key, field.getName()) || StringUtils.equals(key, propertyAnnotation.key()))) { 193 return field; 194 } 195 } 196 return null; 197 } 198 199 private static void configureField(Object check, Field field, String value) { 200 try { 201 field.setAccessible(true); 202 203 if (field.getType().equals(String.class)) { 204 field.set(check, value); 205 206 } else if (int.class == field.getType()) { 207 field.setInt(check, Integer.parseInt(value)); 208 209 } else if (short.class == field.getType()) { 210 field.setShort(check, Short.parseShort(value)); 211 212 } else if (long.class == field.getType()) { 213 field.setLong(check, Long.parseLong(value)); 214 215 } else if (double.class == field.getType()) { 216 field.setDouble(check, Double.parseDouble(value)); 217 218 } else if (boolean.class == field.getType()) { 219 field.setBoolean(check, Boolean.parseBoolean(value)); 220 221 } else if (byte.class == field.getType()) { 222 field.setByte(check, Byte.parseByte(value)); 223 224 } else if (Integer.class == field.getType()) { 225 field.set(check, Integer.parseInt(value)); 226 227 } else if (Long.class == field.getType()) { 228 field.set(check, Long.parseLong(value)); 229 230 } else if (Double.class == field.getType()) { 231 field.set(check, Double.parseDouble(value)); 232 233 } else if (Boolean.class == field.getType()) { 234 field.set(check, Boolean.parseBoolean(value)); 235 236 } else { 237 throw new SonarException("The type of the field " + field + " is not supported: " + field.getType()); 238 } 239 } catch (IllegalAccessException e) { 240 throw new SonarException("Can not set the value of the field " + field + " in the class: " + check.getClass().getName(), e); 241 } 242 } 243}