001 /* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2014 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube 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 * SonarQube 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 */ 020 package org.sonar.api.batch.rule; 021 022 import com.google.common.collect.Maps; 023 import org.apache.commons.lang.StringUtils; 024 import org.sonar.api.rule.RuleKey; 025 import org.sonar.api.utils.AnnotationUtils; 026 import org.sonar.api.utils.FieldUtils2; 027 import org.sonar.api.utils.SonarException; 028 import org.sonar.check.RuleProperty; 029 030 import javax.annotation.CheckForNull; 031 import java.lang.reflect.Field; 032 import java.util.Arrays; 033 import java.util.Collection; 034 import java.util.List; 035 import java.util.Map; 036 037 /** 038 * Instantiates checks (objects that provide implementation of coding 039 * rules) that use sonar-check-api annotations. Checks are selected and configured 040 * from the Quality profiles enabled on the current module. 041 * <p/> 042 * Example of check class: 043 * <pre> 044 * @Rule(key = "S001") 045 * public class CheckS001 { 046 * @RuleProperty 047 * private String pattern; 048 * 049 * public String getPattern() { 050 * return pattern; 051 * } 052 * } 053 * </pre> 054 * How to use: 055 * <pre> 056 * public class MyRuleEngine extends BatchExtension { 057 * private final CheckFactory checkFactory; 058 * 059 * public MyRuleEngine(CheckFactory checkFactory) { 060 * this.checkFactory = checkFactory; 061 * } 062 * 063 * public void execute() { 064 * Checks checks = checkFactory.create("my-rule-repository"); 065 * checks.addAnnotatedChecks(CheckS001.class); 066 * // checks.all() contains an instance of CheckS001 067 * // with field "pattern" set to the value specified in 068 * // the Quality profile 069 * 070 * // Checks are used to detect issues on source code 071 * 072 * // checks.ruleKey(obj) can be used to create the detected 073 * // issues. 074 * } 075 * } 076 * </pre> 077 * <p/> 078 * It replaces org.sonar.api.checks.AnnotationCheckFactory 079 * 080 * @since 4.2 081 */ 082 public class Checks<C> { 083 private final ActiveRules activeRules; 084 private final String repository; 085 private final Map<RuleKey, C> checkByRule = Maps.newHashMap(); 086 private final Map<C, RuleKey> ruleByCheck = Maps.newIdentityHashMap(); 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(Arrays.asList(checkClassesOrObjects)); 114 } 115 116 public Checks<C> addAnnotatedChecks(Collection checkClassesOrObjects) { 117 Map<String, Object> checksByEngineKey = Maps.newHashMap(); 118 for (Object checkClassesOrObject : checkClassesOrObjects) { 119 String engineKey = annotatedEngineKey(checkClassesOrObject); 120 if (engineKey != null) { 121 checksByEngineKey.put(engineKey, checkClassesOrObject); 122 } 123 } 124 125 for (ActiveRule activeRule : activeRules.findByRepository(repository)) { 126 String engineKey = StringUtils.defaultIfBlank(activeRule.internalKey(), activeRule.ruleKey().rule()); 127 Object checkClassesOrObject = checksByEngineKey.get(engineKey); 128 Object obj = instantiate(activeRule, checkClassesOrObject); 129 add(activeRule.ruleKey(), (C) obj); 130 } 131 return this; 132 } 133 134 private String annotatedEngineKey(Object annotatedClassOrObject) { 135 String key = null; 136 org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(annotatedClassOrObject, org.sonar.check.Rule.class); 137 if (ruleAnnotation != null) { 138 key = ruleAnnotation.key(); 139 } 140 Class clazz = annotatedClassOrObject.getClass(); 141 if (annotatedClassOrObject instanceof Class) { 142 clazz = (Class) annotatedClassOrObject; 143 } 144 return StringUtils.defaultIfEmpty(key, clazz.getCanonicalName()); 145 } 146 147 private Object instantiate(ActiveRule activeRule, Object checkClassOrInstance) { 148 try { 149 Object check = checkClassOrInstance; 150 if (check instanceof Class) { 151 check = ((Class) checkClassOrInstance).newInstance(); 152 } 153 configureFields(activeRule, check); 154 return check; 155 } catch (InstantiationException e) { 156 throw failToInstantiateCheck(activeRule, checkClassOrInstance, e); 157 } catch (IllegalAccessException e) { 158 throw failToInstantiateCheck(activeRule, checkClassOrInstance, e); 159 } 160 } 161 162 private RuntimeException failToInstantiateCheck(ActiveRule activeRule, Object checkClassOrInstance, Exception e) { 163 throw new IllegalStateException(String.format("Fail to instantiate class %s for rule %s", checkClassOrInstance, activeRule.ruleKey()), e); 164 } 165 166 private void configureFields(ActiveRule activeRule, Object check) { 167 for (Map.Entry<String, String> param : activeRule.params().entrySet()) { 168 Field field = getField(check, param.getKey()); 169 if (field == null) { 170 throw new IllegalStateException( 171 String.format("The field '%s' does not exist or is not annotated with @RuleProperty in the class %s", param.getKey(), check.getClass().getName())); 172 } 173 if (StringUtils.isNotBlank(param.getValue())) { 174 configureField(check, field, param.getValue()); 175 } 176 } 177 } 178 179 @CheckForNull 180 private Field getField(Object check, String key) { 181 List<Field> fields = FieldUtils2.getFields(check.getClass(), true); 182 for (Field field : fields) { 183 RuleProperty propertyAnnotation = field.getAnnotation(RuleProperty.class); 184 if (propertyAnnotation != null && (StringUtils.equals(key, field.getName()) || StringUtils.equals(key, propertyAnnotation.key()))) { 185 return field; 186 } 187 } 188 return null; 189 } 190 191 private void configureField(Object check, Field field, String value) { 192 try { 193 field.setAccessible(true); 194 195 if (field.getType().equals(String.class)) { 196 field.set(check, value); 197 198 } else if ("int".equals(field.getType().getSimpleName())) { 199 field.setInt(check, Integer.parseInt(value)); 200 201 } else if ("short".equals(field.getType().getSimpleName())) { 202 field.setShort(check, Short.parseShort(value)); 203 204 } else if ("long".equals(field.getType().getSimpleName())) { 205 field.setLong(check, Long.parseLong(value)); 206 207 } else if ("double".equals(field.getType().getSimpleName())) { 208 field.setDouble(check, Double.parseDouble(value)); 209 210 } else if ("boolean".equals(field.getType().getSimpleName())) { 211 field.setBoolean(check, Boolean.parseBoolean(value)); 212 213 } else if ("byte".equals(field.getType().getSimpleName())) { 214 field.setByte(check, Byte.parseByte(value)); 215 216 } else if (field.getType().equals(Integer.class)) { 217 field.set(check, Integer.parseInt(value)); 218 219 } else if (field.getType().equals(Long.class)) { 220 field.set(check, Long.parseLong(value)); 221 222 } else if (field.getType().equals(Double.class)) { 223 field.set(check, Double.parseDouble(value)); 224 225 } else if (field.getType().equals(Boolean.class)) { 226 field.set(check, Boolean.parseBoolean(value)); 227 228 } else { 229 throw new SonarException("The type of the field " + field + " is not supported: " + field.getType()); 230 } 231 } catch (IllegalAccessException e) { 232 throw new SonarException("Can not set the value of the field " + field + " in the class: " + check.getClass().getName(), e); 233 } 234 } 235 }