001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info 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.server.rule; 021 022import com.google.common.annotations.VisibleForTesting; 023import com.google.common.base.Function; 024import com.google.common.base.Functions; 025import com.google.common.collect.ImmutableMap; 026import java.lang.reflect.Field; 027import java.util.List; 028import javax.annotation.CheckForNull; 029import org.apache.commons.lang.StringUtils; 030import org.sonar.api.rule.RuleStatus; 031import org.sonar.api.utils.AnnotationUtils; 032import org.sonar.api.utils.FieldUtils2; 033import org.sonar.api.utils.log.Logger; 034import org.sonar.api.utils.log.Loggers; 035import org.sonar.check.Cardinality; 036 037/** 038 * Read definitions of rules based on the annotations provided by sonar-check-api. It is used 039 * to feed {@link RulesDefinition}. 040 * 041 * @see org.sonar.api.server.rule.RulesDefinition 042 * @since 4.3 043 */ 044public class RulesDefinitionAnnotationLoader { 045 046 private static final Logger LOG = Loggers.get(RulesDefinitionAnnotationLoader.class); 047 048 private static final Function<Class<?>, RuleParamType> TYPE_FOR_CLASS = Functions.forMap( 049 ImmutableMap.<Class<?>, RuleParamType>builder() 050 .put(Integer.class, RuleParamType.INTEGER) 051 .put(int.class, RuleParamType.INTEGER) 052 .put(Float.class, RuleParamType.FLOAT) 053 .put(float.class, RuleParamType.FLOAT) 054 .put(Boolean.class, RuleParamType.BOOLEAN) 055 .put(boolean.class, RuleParamType.BOOLEAN) 056 .build(), 057 RuleParamType.STRING); 058 059 public void load(RulesDefinition.NewExtendedRepository repo, Class... annotatedClasses) { 060 for (Class annotatedClass : annotatedClasses) { 061 loadRule(repo, annotatedClass); 062 } 063 } 064 065 @CheckForNull 066 RulesDefinition.NewRule loadRule(RulesDefinition.NewExtendedRepository repo, Class clazz) { 067 org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(clazz, org.sonar.check.Rule.class); 068 if (ruleAnnotation != null) { 069 return loadRule(repo, clazz, ruleAnnotation); 070 } else { 071 LOG.warn("The class " + clazz.getCanonicalName() + " should be annotated with " + org.sonar.check.Rule.class); 072 return null; 073 } 074 } 075 076 private static RulesDefinition.NewRule loadRule(RulesDefinition.NewExtendedRepository repo, Class clazz, org.sonar.check.Rule ruleAnnotation) { 077 String ruleKey = StringUtils.defaultIfEmpty(ruleAnnotation.key(), clazz.getCanonicalName()); 078 String ruleName = StringUtils.defaultIfEmpty(ruleAnnotation.name(), null); 079 String description = StringUtils.defaultIfEmpty(ruleAnnotation.description(), null); 080 081 RulesDefinition.NewRule rule = repo.createRule(ruleKey); 082 rule.setName(ruleName).setHtmlDescription(description); 083 rule.setSeverity(ruleAnnotation.priority().name()); 084 rule.setTemplate(ruleAnnotation.cardinality() == Cardinality.MULTIPLE); 085 rule.setStatus(RuleStatus.valueOf(ruleAnnotation.status())); 086 rule.setTags(ruleAnnotation.tags()); 087 088 List<Field> fields = FieldUtils2.getFields(clazz, true); 089 for (Field field : fields) { 090 loadParameters(rule, field); 091 } 092 093 return rule; 094 } 095 096 private static void loadParameters(RulesDefinition.NewRule rule, Field field) { 097 org.sonar.check.RuleProperty propertyAnnotation = field.getAnnotation(org.sonar.check.RuleProperty.class); 098 if (propertyAnnotation != null) { 099 String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName()); 100 RulesDefinition.NewParam param = rule.createParam(fieldKey) 101 .setDescription(propertyAnnotation.description()) 102 .setDefaultValue(propertyAnnotation.defaultValue()); 103 104 if (!StringUtils.isBlank(propertyAnnotation.type())) { 105 try { 106 param.setType(RuleParamType.parse(propertyAnnotation.type().trim())); 107 } catch (IllegalArgumentException e) { 108 throw new IllegalArgumentException("Invalid property type [" + propertyAnnotation.type() + "]", e); 109 } 110 } else { 111 param.setType(guessType(field.getType())); 112 } 113 } 114 } 115 116 @VisibleForTesting 117 static RuleParamType guessType(Class<?> type) { 118 return TYPE_FOR_CLASS.apply(type); 119 } 120}