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