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.profiles;
021
022import com.google.common.base.Function;
023import com.google.common.base.Predicate;
024import com.google.common.collect.Iterables;
025import com.google.common.collect.Lists;
026import java.util.ArrayList;
027import java.util.List;
028import javax.annotation.CheckForNull;
029import javax.annotation.Nullable;
030import org.apache.commons.lang.StringUtils;
031import org.apache.commons.lang.builder.EqualsBuilder;
032import org.apache.commons.lang.builder.HashCodeBuilder;
033import org.sonar.api.rules.ActiveRule;
034import org.sonar.api.rules.Rule;
035import org.sonar.api.rules.RulePriority;
036import org.sonar.api.utils.MessageException;
037
038/**
039 * This class is badly named. It should be "QualityProfile". Indeed it does not relate only to rules but to metric thresholds too.
040 */
041public class RulesProfile implements Cloneable {
042
043  /**
044   * Name of the default profile "Sonar Way"
045   * @deprecated in 4.2. Use your own constant.
046   */
047  @Deprecated
048  public static final String SONAR_WAY_NAME = "Sonar way";
049
050  /**
051   * Name of the default java profile "Sonar way with Findbugs"
052   * @deprecated in 4.2. Use your own constant.
053   */
054  @Deprecated
055  public static final String SONAR_WAY_FINDBUGS_NAME = "Sonar way with Findbugs";
056
057  /**
058   * Name of the default java profile "Sun checks"
059   * @deprecated in 4.2. Use your own constant.
060   */
061  @Deprecated
062  public static final String SUN_CONVENTIONS_NAME = "Sun checks";
063
064  private String name;
065  private Boolean defaultProfile = Boolean.FALSE;
066  private String language;
067  private String parentName;
068  private List<ActiveRule> activeRules = new ArrayList<>();
069
070  /**
071   * @deprecated use the factory method create()
072   */
073  @Deprecated
074  public RulesProfile() {
075  }
076
077  /**
078   * @deprecated since 2.3. Use the factory method create()
079   */
080  @Deprecated
081  public RulesProfile(String name, String language) {
082    this.name = name;
083    this.language = language;
084    this.activeRules = new ArrayList<>();
085  }
086
087  /**
088   * @deprecated since 2.3. Use the factory method create()
089   */
090  @Deprecated
091  public RulesProfile(String name, String language, boolean defaultProfile, /* kept for backward-compatibility */boolean provided) {
092    this(name, language);
093    this.defaultProfile = defaultProfile;
094  }
095
096  public Integer getId() {
097    return null;
098  }
099
100  /**
101   * @return the profile name, unique by language.
102   */
103  public String getName() {
104    return name;
105  }
106
107  /**
108   * Set the profile name.
109   */
110  public RulesProfile setName(String s) {
111    this.name = s;
112    return this;
113  }
114
115  /**
116   * @deprecated profile versioning is dropped in 4.4. Always returns -1.
117   */
118  @Deprecated
119  public int getVersion() {
120    return -1;
121  }
122
123  /**
124   * @deprecated profile versioning is dropped in 4.4. Always returns -1.
125   */
126  @Deprecated
127  public RulesProfile setVersion(int version) {
128    // ignore
129    return this;
130  }
131
132  /**
133   * @deprecated profile versioning is dropped in 4.4. Always returns null.
134   */
135  @CheckForNull
136  @Deprecated
137  public Boolean getUsed() {
138    return null;
139  }
140
141  /**
142   * @deprecated profile versioning is dropped in 4.4. Always returns -1.
143   */
144  @Deprecated
145  public RulesProfile setUsed(Boolean used) {
146    return this;
147  }
148
149  /**
150   * @return the list of active rules
151   */
152  public List<ActiveRule> getActiveRules() {
153    return getActiveRules(false);
154  }
155
156  /**
157   * @return the list of active rules
158   */
159  public List<ActiveRule> getActiveRules(boolean acceptDisabledRules) {
160    if (acceptDisabledRules) {
161      return activeRules;
162    }
163    List<ActiveRule> result = new ArrayList<>();
164    for (ActiveRule activeRule : activeRules) {
165      if (activeRule.isEnabled()) {
166        result.add(activeRule);
167      }
168    }
169    return result;
170  }
171
172  public RulesProfile removeActiveRule(ActiveRule activeRule) {
173    activeRules.remove(activeRule);
174    return this;
175  }
176
177  public RulesProfile addActiveRule(ActiveRule activeRule) {
178    activeRules.add(activeRule);
179    return this;
180  }
181
182  /**
183   * Set the list of active rules
184   */
185  public void setActiveRules(List<ActiveRule> activeRules) {
186    this.activeRules = activeRules;
187  }
188
189  /**
190   * @return whether this is the default profile for the language
191   */
192  public Boolean getDefaultProfile() {
193    return defaultProfile;
194  }
195
196  /**
197   * Set whether this is the default profile for the language. The default profile is used when none is explicitly defined when auditing a
198   * project.
199   */
200  public void setDefaultProfile(Boolean b) {
201    this.defaultProfile = b;
202  }
203
204  /**
205   * @return the profile language
206   */
207  public String getLanguage() {
208    return language;
209  }
210
211  /**
212   * Set the profile language
213   */
214  public RulesProfile setLanguage(String s) {
215    this.language = s;
216    return this;
217  }
218
219  /**
220   * For internal use only.
221   *
222   * @since 2.5
223   */
224  @CheckForNull
225  public String getParentName() {
226    return parentName;
227  }
228
229  /**
230   * For internal use only.
231   *
232   * @since 2.5
233   */
234  public void setParentName(String parentName) {
235    this.parentName = parentName;
236  }
237
238  /**
239   * Note: disabled rules are excluded.
240   *
241   * @return the list of active rules for a given severity
242   */
243  public List<ActiveRule> getActiveRules(RulePriority severity) {
244    List<ActiveRule> result = new ArrayList<>();
245    for (ActiveRule activeRule : activeRules) {
246      if (activeRule.getSeverity().equals(severity) && activeRule.isEnabled()) {
247        result.add(activeRule);
248      }
249    }
250    return result;
251  }
252
253  /**
254   * Get the active rules of a specific repository.
255   * Only enabled rules are selected. Disabled rules are excluded.
256   */
257  public List<ActiveRule> getActiveRulesByRepository(String repositoryKey) {
258    List<ActiveRule> result = new ArrayList<>();
259    for (ActiveRule activeRule : activeRules) {
260      if (repositoryKey.equals(activeRule.getRepositoryKey()) && activeRule.isEnabled()) {
261        result.add(activeRule);
262      }
263    }
264    return result;
265  }
266
267  /**
268   * Note: disabled rules are excluded.
269   *
270   * @return an active rule from a plugin key and a rule key if the rule is activated, null otherwise
271   */
272  @CheckForNull
273  public ActiveRule getActiveRule(String repositoryKey, String ruleKey) {
274    for (ActiveRule activeRule : activeRules) {
275      if (StringUtils.equals(activeRule.getRepositoryKey(), repositoryKey) && StringUtils.equals(activeRule.getRuleKey(), ruleKey) && activeRule.isEnabled()) {
276        return activeRule;
277      }
278    }
279    return null;
280  }
281
282  /**
283   * Note: disabled rules are excluded.
284   */
285  @CheckForNull
286  public ActiveRule getActiveRuleByConfigKey(String repositoryKey, String configKey) {
287    for (ActiveRule activeRule : activeRules) {
288      if (StringUtils.equals(activeRule.getRepositoryKey(), repositoryKey) && StringUtils.equals(activeRule.getConfigKey(), configKey) && activeRule.isEnabled()) {
289        return activeRule;
290      }
291    }
292    return null;
293  }
294
295  /**
296   * Note: disabled rules are excluded.
297   */
298  @CheckForNull
299  public ActiveRule getActiveRule(Rule rule) {
300    return getActiveRule(rule.getRepositoryKey(), rule.getKey());
301  }
302
303  /**
304   * @param optionalSeverity if null, then the default rule severity is used
305   */
306  public ActiveRule activateRule(final Rule rule, @Nullable RulePriority optionalSeverity) {
307    if (Iterables.any(activeRules, new MatchRule(rule))) {
308      throw MessageException.of(String.format(
309        "The definition of the profile '%s' (language '%s') contains multiple occurrences of the '%s:%s' rule. The plugin which declares this profile should fix this.",
310        getName(), getLanguage(), rule.getRepositoryKey(), rule.getKey()));
311    }
312    ActiveRule activeRule = new ActiveRule();
313    activeRule.setRule(rule);
314    activeRule.setRulesProfile(this);
315    activeRule.setSeverity(optionalSeverity == null ? rule.getSeverity() : optionalSeverity);
316    activeRules.add(activeRule);
317    return activeRule;
318  }
319
320  @Override
321  public boolean equals(Object obj) {
322    if (!(obj instanceof RulesProfile)) {
323      return false;
324    }
325    if (this == obj) {
326      return true;
327    }
328    RulesProfile other = (RulesProfile) obj;
329    return new EqualsBuilder().append(language, other.getLanguage()).append(name, other.getName()).isEquals();
330  }
331
332  @Override
333  public int hashCode() {
334    return new HashCodeBuilder(17, 37).append(language).append(name).toHashCode();
335  }
336
337  @Override
338  public Object clone() {
339    RulesProfile clone = RulesProfile.create(getName(), getLanguage());
340    clone.setDefaultProfile(getDefaultProfile());
341    clone.setParentName(getParentName());
342    if (activeRules != null && !activeRules.isEmpty()) {
343      clone.setActiveRules(Lists.transform(activeRules, CloneFunction.INSTANCE));
344    }
345    return clone;
346  }
347
348  @Override
349  public String toString() {
350    return new StringBuilder().append("[name=").append(name).append(",language=").append(language).append("]").toString();
351  }
352
353  public static RulesProfile create(String name, String language) {
354    return new RulesProfile().setName(name).setLanguage(language);
355  }
356
357  public static RulesProfile create() {
358    return new RulesProfile();
359  }
360
361  private static class MatchRule implements Predicate<ActiveRule> {
362    private final Rule rule;
363
364    public MatchRule(Rule rule) {
365      this.rule = rule;
366    }
367
368    @Override
369    public boolean apply(ActiveRule input) {
370      return input.getRule().equals(rule);
371    }
372  }
373
374  private enum CloneFunction implements Function<ActiveRule, ActiveRule> {
375    INSTANCE;
376    @Override
377    public ActiveRule apply(ActiveRule input) {
378      return (ActiveRule) input.clone();
379    }
380  }
381}