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
032 import java.lang.reflect.Field;
033 import java.util.Arrays;
034 import java.util.Collection;
035 import java.util.List;
036 import java.util.Map;
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 * <p/>
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 * <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.templateRuleKey(), activeRule.ruleKey().rule());
127 Object checkClassesOrObject = checksByEngineKey.get(engineKey);
128 if (checkClassesOrObject != null) {
129 Object obj = instantiate(activeRule, checkClassesOrObject);
130 add(activeRule.ruleKey(), (C) obj);
131 }
132 }
133 return this;
134 }
135
136 private String annotatedEngineKey(Object annotatedClassOrObject) {
137 String key = null;
138 org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(annotatedClassOrObject, org.sonar.check.Rule.class);
139 if (ruleAnnotation != null) {
140 key = ruleAnnotation.key();
141 }
142 Class clazz = annotatedClassOrObject.getClass();
143 if (annotatedClassOrObject instanceof Class) {
144 clazz = (Class) annotatedClassOrObject;
145 }
146 return StringUtils.defaultIfEmpty(key, clazz.getCanonicalName());
147 }
148
149 private Object instantiate(ActiveRule activeRule, Object checkClassOrInstance) {
150 try {
151 Object check = checkClassOrInstance;
152 if (check instanceof Class) {
153 check = ((Class) checkClassOrInstance).newInstance();
154 }
155 configureFields(activeRule, check);
156 return check;
157 } catch (InstantiationException e) {
158 throw failToInstantiateCheck(activeRule, checkClassOrInstance, e);
159 } catch (IllegalAccessException e) {
160 throw failToInstantiateCheck(activeRule, checkClassOrInstance, e);
161 }
162 }
163
164 private RuntimeException failToInstantiateCheck(ActiveRule activeRule, Object checkClassOrInstance, Exception e) {
165 throw new IllegalStateException(String.format("Fail to instantiate class %s for rule %s", checkClassOrInstance, activeRule.ruleKey()), e);
166 }
167
168 private void configureFields(ActiveRule activeRule, Object check) {
169 for (Map.Entry<String, String> param : activeRule.params().entrySet()) {
170 Field field = getField(check, param.getKey());
171 if (field == null) {
172 throw new IllegalStateException(
173 String.format("The field '%s' does not exist or is not annotated with @RuleProperty in the class %s", param.getKey(), check.getClass().getName()));
174 }
175 if (StringUtils.isNotBlank(param.getValue())) {
176 configureField(check, field, param.getValue());
177 }
178 }
179 }
180
181 @CheckForNull
182 private Field getField(Object check, String key) {
183 List<Field> fields = FieldUtils2.getFields(check.getClass(), true);
184 for (Field field : fields) {
185 RuleProperty propertyAnnotation = field.getAnnotation(RuleProperty.class);
186 if (propertyAnnotation != null && (StringUtils.equals(key, field.getName()) || StringUtils.equals(key, propertyAnnotation.key()))) {
187 return field;
188 }
189 }
190 return null;
191 }
192
193 private void configureField(Object check, Field field, String value) {
194 try {
195 field.setAccessible(true);
196
197 if (field.getType().equals(String.class)) {
198 field.set(check, value);
199
200 } else if ("int".equals(field.getType().getSimpleName())) {
201 field.setInt(check, Integer.parseInt(value));
202
203 } else if ("short".equals(field.getType().getSimpleName())) {
204 field.setShort(check, Short.parseShort(value));
205
206 } else if ("long".equals(field.getType().getSimpleName())) {
207 field.setLong(check, Long.parseLong(value));
208
209 } else if ("double".equals(field.getType().getSimpleName())) {
210 field.setDouble(check, Double.parseDouble(value));
211
212 } else if ("boolean".equals(field.getType().getSimpleName())) {
213 field.setBoolean(check, Boolean.parseBoolean(value));
214
215 } else if ("byte".equals(field.getType().getSimpleName())) {
216 field.setByte(check, Byte.parseByte(value));
217
218 } else if (field.getType().equals(Integer.class)) {
219 field.set(check, Integer.parseInt(value));
220
221 } else if (field.getType().equals(Long.class)) {
222 field.set(check, Long.parseLong(value));
223
224 } else if (field.getType().equals(Double.class)) {
225 field.set(check, Double.parseDouble(value));
226
227 } else if (field.getType().equals(Boolean.class)) {
228 field.set(check, Boolean.parseBoolean(value));
229
230 } else {
231 throw new SonarException("The type of the field " + field + " is not supported: " + field.getType());
232 }
233 } catch (IllegalAccessException e) {
234 throw new SonarException("Can not set the value of the field " + field + " in the class: " + check.getClass().getName(), e);
235 }
236 }
237 }