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    }