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.server.rule;
021    
022    import com.google.common.base.Strings;
023    import com.google.common.collect.*;
024    import org.apache.commons.io.IOUtils;
025    import org.apache.commons.lang.StringUtils;
026    import org.slf4j.LoggerFactory;
027    import org.sonar.api.ServerExtension;
028    import org.sonar.api.rule.RuleStatus;
029    import org.sonar.api.rule.Severity;
030    import org.sonar.api.server.debt.DebtRemediationFunction;
031    
032    import javax.annotation.CheckForNull;
033    import javax.annotation.Nullable;
034    import javax.annotation.concurrent.Immutable;
035    
036    import java.io.IOException;
037    import java.net.URL;
038    import java.util.Collection;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.Set;
042    
043    /**
044     * Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of
045     * this extension point in order to define the rules that it supports.
046     * <p/>
047     * This interface replaces the deprecated class org.sonar.api.rules.RuleRepository.
048     * <p/>
049     * <h3>How to use</h3>
050     * <pre>
051     * public class MyJsRulesDefinition implements RulesDefinition {
052     *
053     *   {@literal @}Override
054     *   public void define(Context context) {
055     *     NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
056     *
057     *     // define a rule programmatically. Note that rules
058     *     // could be loaded from files (JSON, XML, ...)
059     *     NewRule x1Rule = repository.createRule("x1")
060     *      .setName("No empty line")
061     *      .setHtmlDescription("Generate an issue on empty lines")
062     *
063     *      // optional tags
064     *      .setTags("style", "stupid")
065     *
066     *     // optional status. Default value is READY.
067     *     .setStatus(RuleStatus.BETA)
068     *
069     *     // default severity when the rule is activated on a Quality profile. Default value is MAJOR.
070     *     .setSeverity(Severity.MINOR);
071     *
072     *     x1Rule
073     *       .setDebtSubCharacteristic("INTEGRATION_TESTABILITY")
074     *       .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min"));
075     *
076     *     x1Rule.createParam("acceptWhitespace")
077     *       .setDefaultValue("false")
078     *       .setType(RuleParamType.BOOLEAN)
079     *       .setDescription("Accept whitespaces on the line");
080     *
081     *     // don't forget to call done() to finalize the definition
082     *     repository.done();
083     *   }
084     * }
085     * </pre>
086     * <p/>
087     * If rules are declared in a XML file with the standard SonarQube format (see
088     * {@link org.sonar.api.server.rule.RulesDefinitionXmlLoader}), then it can be loaded by using :
089     * <p/>
090     * <pre>
091     * public class MyJsRulesDefinition implements RulesDefinition {
092     *
093     *   private final RulesDefinitionXmlLoader xmlLoader;
094     *
095     *   public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
096     *     this.xmlLoader = xmlLoader;
097     *   }
098     *
099     *   {@literal @}Override
100     *   public void define(Context context) {
101     *     NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
102     *     // see javadoc of RulesDefinitionXmlLoader for the format
103     *     xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
104     *     repository.done();
105     *   }
106     * }
107     * </pre>
108     * <p/>
109     * In the above example, XML file must contain name and description of each rule. If it's not the case, then the
110     * (deprecated) English bundles can be used :
111     * <p/>
112     * <pre>
113     * public class MyJsRulesDefinition implements RulesDefinition {
114     *
115     *   private final RulesDefinitionXmlLoader xmlLoader;
116     *   private final RulesDefinitionI18nLoader i18nLoader;
117     *
118     *   public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) {
119     *     this.xmlLoader = xmlLoader;
120     *     this.i18nLoader = i18nLoader;
121     *   }
122     *
123     *   {@literal @}Override
124     *   public void define(Context context) {
125     *     NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
126     *     xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"), "UTF-8");
127     *     i18nLoader.load(repository);
128     *     repository.done();
129     *   }
130     * }
131     * </pre>
132     *
133     * @since 4.3
134     */
135    public interface RulesDefinition extends ServerExtension {
136    
137      /**
138       * Default sub-characteristics of technical debt model. See http://www.sqale.org
139       */
140      final class SubCharacteristics {
141        /**
142         * Related to characteristic REUSABILITY
143         */
144        public static final String MODULARITY = "MODULARITY";
145    
146        /**
147         * Related to characteristic REUSABILITY
148         */
149        public static final String TRANSPORTABILITY = "TRANSPORTABILITY";
150    
151        /**
152         * Related to characteristic PORTABILITY
153         */
154        public static final String COMPILER_RELATED_PORTABILITY = "COMPILER_RELATED_PORTABILITY";
155    
156        /**
157         * Related to characteristic PORTABILITY
158         */
159        public static final String HARDWARE_RELATED_PORTABILITY = "HARDWARE_RELATED_PORTABILITY";
160    
161        /**
162         * Related to characteristic PORTABILITY
163         */
164        public static final String LANGUAGE_RELATED_PORTABILITY = "LANGUAGE_RELATED_PORTABILITY";
165    
166        /**
167         * Related to characteristic PORTABILITY
168         */
169        public static final String OS_RELATED_PORTABILITY = "OS_RELATED_PORTABILITY";
170    
171        /**
172         * Related to characteristic PORTABILITY
173         */
174        public static final String SOFTWARE_RELATED_PORTABILITY = "SOFTWARE_RELATED_PORTABILITY";
175    
176        /**
177         * Related to characteristic PORTABILITY
178         */
179        public static final String TIME_ZONE_RELATED_PORTABILITY = "TIME_ZONE_RELATED_PORTABILITY";
180    
181        /**
182         * Related to characteristic MAINTAINABILITY
183         */
184        public static final String READABILITY = "READABILITY";
185    
186        /**
187         * Related to characteristic MAINTAINABILITY
188         */
189        public static final String UNDERSTANDABILITY = "UNDERSTANDABILITY";
190    
191        /**
192         * Related to characteristic SECURITY
193         */
194        public static final String API_ABUSE = "API_ABUSE";
195    
196        /**
197         * Related to characteristic SECURITY
198         */
199        public static final String ERRORS = "ERRORS";
200    
201        /**
202         * Related to characteristic SECURITY
203         */
204        public static final String INPUT_VALIDATION_AND_REPRESENTATION = "INPUT_VALIDATION_AND_REPRESENTATION";
205    
206        /**
207         * Related to characteristic SECURITY
208         */
209        public static final String SECURITY_FEATURES = "SECURITY_FEATURES";
210    
211        /**
212         * Related to characteristic EFFICIENCY
213         */
214        public static final String CPU_EFFICIENCY = "CPU_EFFICIENCY";
215    
216        /**
217         * Related to characteristic EFFICIENCY
218         */
219        public static final String MEMORY_EFFICIENCY = "MEMORY_EFFICIENCY";
220    
221        /**
222         * Related to characteristic EFFICIENCY
223         */
224        public static final String NETWORK_USE = "NETWORK_USE";
225    
226        /**
227         * Related to characteristic CHANGEABILITY
228         */
229        public static final String ARCHITECTURE_CHANGEABILITY = "ARCHITECTURE_CHANGEABILITY";
230    
231        /**
232         * Related to characteristic CHANGEABILITY
233         */
234        public static final String DATA_CHANGEABILITY = "DATA_CHANGEABILITY";
235    
236        /**
237         * Related to characteristic CHANGEABILITY
238         */
239        public static final String LOGIC_CHANGEABILITY = "LOGIC_CHANGEABILITY";
240    
241        /**
242         * Related to characteristic RELIABILITY
243         */
244        public static final String ARCHITECTURE_RELIABILITY = "ARCHITECTURE_RELIABILITY";
245    
246        /**
247         * Related to characteristic RELIABILITY
248         */
249        public static final String DATA_RELIABILITY = "DATA_RELIABILITY";
250    
251        /**
252         * Related to characteristic RELIABILITY
253         */
254        public static final String EXCEPTION_HANDLING = "EXCEPTION_HANDLING";
255    
256        /**
257         * Related to characteristic RELIABILITY
258         */
259        public static final String FAULT_TOLERANCE = "FAULT_TOLERANCE";
260    
261        /**
262         * Related to characteristic RELIABILITY
263         */
264        public static final String INSTRUCTION_RELIABILITY = "INSTRUCTION_RELIABILITY";
265    
266        /**
267         * Related to characteristic RELIABILITY
268         */
269        public static final String LOGIC_RELIABILITY = "LOGIC_RELIABILITY";
270    
271        /**
272         * Related to characteristic RELIABILITY
273         */
274        public static final String RESOURCE_RELIABILITY = "RESOURCE_RELIABILITY";
275    
276        /**
277         * Related to characteristic RELIABILITY
278         */
279        public static final String SYNCHRONIZATION_RELIABILITY = "SYNCHRONIZATION_RELIABILITY";
280    
281        /**
282         * Related to characteristic RELIABILITY
283         */
284        public static final String UNIT_TESTS = "UNIT_TESTS";
285    
286        /**
287         * Related to characteristic TESTABILITY
288         */
289        public static final String INTEGRATION_TESTABILITY = "INTEGRATION_TESTABILITY";
290    
291        /**
292         * Related to characteristic TESTABILITY
293         */
294        public static final String UNIT_TESTABILITY = "UNIT_TESTABILITY";
295    
296        /**
297         * Related to characteristic ACCESSIBILITY
298         */
299        public static final String USABILITY_ACCESSIBILITY = "USABILITY_ACCESSIBILITY";
300    
301        /**
302         * Related to characteristic ACCESSIBILITY
303         */
304        public static final String USABILITY_COMPLIANCE = "USABILITY_COMPLIANCE";
305    
306        /**
307         * Related to characteristic ACCESSIBILITY
308         */
309        public static final String USABILITY_EASE_OF_USE = "USABILITY_EASE_OF_USE";
310    
311        /**
312         * Related to characteristic REUSABILITY
313         */
314        public static final String REUSABILITY_COMPLIANCE = "REUSABILITY_COMPLIANCE";
315    
316        /**
317         * Related to characteristic PORTABILITY
318         */
319        public static final String PORTABILITY_COMPLIANCE = "PORTABILITY_COMPLIANCE";
320    
321        /**
322         * Related to characteristic MAINTAINABILITY
323         */
324        public static final String MAINTAINABILITY_COMPLIANCE = "MAINTAINABILITY_COMPLIANCE";
325    
326        /**
327         * Related to characteristic SECURITY
328         */
329        public static final String SECURITY_COMPLIANCE = "SECURITY_COMPLIANCE";
330    
331        /**
332         * Related to characteristic EFFICIENCY
333         */
334        public static final String EFFICIENCY_COMPLIANCE = "EFFICIENCY_COMPLIANCE";
335    
336        /**
337         * Related to characteristic CHANGEABILITY
338         */
339        public static final String CHANGEABILITY_COMPLIANCE = "CHANGEABILITY_COMPLIANCE";
340    
341        /**
342         * Related to characteristic RELIABILITY
343         */
344        public static final String RELIABILITY_COMPLIANCE = "RELIABILITY_COMPLIANCE";
345    
346        /**
347         * Related to characteristic TESTABILITY
348         */
349        public static final String TESTABILITY_COMPLIANCE = "TESTABILITY_COMPLIANCE";
350    
351        private SubCharacteristics() {
352          // only constants
353        }
354      }
355    
356      /**
357       * Instantiated by core but not by plugins
358       */
359      class Context {
360        private final Map<String, Repository> repositoriesByKey = Maps.newHashMap();
361        private final ListMultimap<String, ExtendedRepository> extendedRepositoriesByKey = ArrayListMultimap.create();
362    
363        public NewRepository createRepository(String key, String language) {
364          return new NewRepositoryImpl(this, key, language, false);
365        }
366    
367        public NewExtendedRepository extendRepository(String key, String language) {
368          return new NewRepositoryImpl(this, key, language, true);
369        }
370    
371        @CheckForNull
372        public Repository repository(String key) {
373          return repositoriesByKey.get(key);
374        }
375    
376        public List<Repository> repositories() {
377          return ImmutableList.copyOf(repositoriesByKey.values());
378        }
379    
380        public List<ExtendedRepository> extendedRepositories(String repositoryKey) {
381          return ImmutableList.copyOf(extendedRepositoriesByKey.get(repositoryKey));
382        }
383    
384        public List<ExtendedRepository> extendedRepositories() {
385          return ImmutableList.copyOf(extendedRepositoriesByKey.values());
386        }
387    
388        private void registerRepository(NewRepositoryImpl newRepository) {
389          if (repositoriesByKey.containsKey(newRepository.key)) {
390            throw new IllegalStateException(String.format("The rule repository '%s' is defined several times", newRepository.key));
391          }
392          repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
393        }
394    
395        private void registerExtendedRepository(NewRepositoryImpl newRepository) {
396          extendedRepositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
397        }
398      }
399    
400      interface NewExtendedRepository {
401        NewRule createRule(String ruleKey);
402    
403        @CheckForNull
404        NewRule rule(String ruleKey);
405    
406        Collection<NewRule> rules();
407    
408        String key();
409    
410        void done();
411      }
412    
413      interface NewRepository extends NewExtendedRepository {
414        NewRepository setName(String s);
415      }
416    
417      class NewRepositoryImpl implements NewRepository {
418        private final Context context;
419        private final boolean extended;
420        private final String key;
421        private String language;
422        private String name;
423        private final Map<String, NewRule> newRules = Maps.newHashMap();
424    
425        private NewRepositoryImpl(Context context, String key, String language, boolean extended) {
426          this.extended = extended;
427          this.context = context;
428          this.key = this.name = key;
429          this.language = language;
430        }
431    
432        @Override
433        public String key() {
434          return key;
435        }
436    
437        @Override
438        public NewRepositoryImpl setName(@Nullable String s) {
439          if (StringUtils.isNotEmpty(s)) {
440            this.name = s;
441          }
442          return this;
443        }
444    
445        @Override
446        public NewRule createRule(String ruleKey) {
447          if (newRules.containsKey(ruleKey)) {
448            // Should fail in a perfect world, but at the time being the Findbugs plugin
449            // defines several times the rule EC_INCOMPATIBLE_ARRAY_COMPARE
450            // See http://jira.codehaus.org/browse/SONARJAVA-428
451            LoggerFactory.getLogger(getClass()).warn(String.format("The rule '%s' of repository '%s' is declared several times", ruleKey, key));
452          }
453          NewRule newRule = new NewRule(key, ruleKey);
454          newRules.put(ruleKey, newRule);
455          return newRule;
456        }
457    
458        @CheckForNull
459        @Override
460        public NewRule rule(String ruleKey) {
461          return newRules.get(ruleKey);
462        }
463    
464        @Override
465        public Collection<NewRule> rules() {
466          return newRules.values();
467        }
468    
469        @Override
470        public void done() {
471          // note that some validations can be done here, for example for
472          // verifying that at least one rule is declared
473    
474          if (extended) {
475            context.registerExtendedRepository(this);
476          } else {
477            context.registerRepository(this);
478          }
479        }
480      }
481    
482      interface ExtendedRepository {
483        String key();
484    
485        String language();
486    
487        @CheckForNull
488        Rule rule(String ruleKey);
489    
490        List<Rule> rules();
491      }
492    
493      interface Repository extends ExtendedRepository {
494        String name();
495      }
496    
497      @Immutable
498      class RepositoryImpl implements Repository {
499        private final String key, language, name;
500        private final Map<String, Rule> rulesByKey;
501    
502        private RepositoryImpl(NewRepositoryImpl newRepository) {
503          this.key = newRepository.key;
504          this.language = newRepository.language;
505          this.name = newRepository.name;
506          ImmutableMap.Builder<String, Rule> ruleBuilder = ImmutableMap.builder();
507          for (NewRule newRule : newRepository.newRules.values()) {
508            newRule.validate();
509            ruleBuilder.put(newRule.key, new Rule(this, newRule));
510          }
511          this.rulesByKey = ruleBuilder.build();
512        }
513    
514        @Override
515        public String key() {
516          return key;
517        }
518    
519        @Override
520        public String language() {
521          return language;
522        }
523    
524        @Override
525        public String name() {
526          return name;
527        }
528    
529        @Override
530        @CheckForNull
531        public Rule rule(String ruleKey) {
532          return rulesByKey.get(ruleKey);
533        }
534    
535        @Override
536        public List<Rule> rules() {
537          return ImmutableList.copyOf(rulesByKey.values());
538        }
539    
540        @Override
541        public boolean equals(Object o) {
542          if (this == o) {
543            return true;
544          }
545          if (o == null || getClass() != o.getClass()) {
546            return false;
547          }
548          RepositoryImpl that = (RepositoryImpl) o;
549          return key.equals(that.key);
550        }
551    
552        @Override
553        public int hashCode() {
554          return key.hashCode();
555        }
556      }
557    
558      /**
559       * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
560       */
561      interface DebtRemediationFunctions {
562        DebtRemediationFunction linear(String coefficient);
563    
564        DebtRemediationFunction linearWithOffset(String coefficient, String offset);
565    
566        DebtRemediationFunction constantPerIssue(String offset);
567      }
568    
569      class NewRule {
570        private final String repoKey, key;
571        private String name, htmlDescription, markdownDescription, internalKey, severity = Severity.MAJOR;
572        private boolean template;
573        private RuleStatus status = RuleStatus.defaultStatus();
574        private String debtSubCharacteristic;
575        private DebtRemediationFunction debtRemediationFunction;
576        private String effortToFixDescription;
577        private final Set<String> tags = Sets.newTreeSet();
578        private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
579        private final DebtRemediationFunctions functions;
580    
581        private NewRule(String repoKey, String key) {
582          this.repoKey = repoKey;
583          this.key = key;
584          this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
585        }
586    
587        public String key() {
588          return this.key;
589        }
590    
591        /**
592         * Required rule name
593         */
594        public NewRule setName(@Nullable String s) {
595          this.name = StringUtils.trimToNull(s);
596          return this;
597        }
598    
599        public NewRule setTemplate(boolean template) {
600          this.template = template;
601          return this;
602        }
603    
604        public NewRule setSeverity(String s) {
605          if (!Severity.ALL.contains(s)) {
606            throw new IllegalArgumentException(String.format("Severity of rule %s is not correct: %s", this, s));
607          }
608          this.severity = s;
609          return this;
610        }
611    
612        public NewRule setHtmlDescription(@Nullable String s) {
613          if (markdownDescription != null) {
614            throw new IllegalStateException(String.format("Rule '%s' already has a Markdown description", this));
615          }
616          this.htmlDescription = StringUtils.trimToNull(s);
617          return this;
618        }
619    
620        /**
621         * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code>
622         */
623        public NewRule setHtmlDescription(@Nullable URL classpathUrl) {
624          if (classpathUrl != null) {
625            try {
626              setHtmlDescription(IOUtils.toString(classpathUrl));
627            } catch (IOException e) {
628              throw new IllegalStateException("Fail to read: " + classpathUrl, e);
629            }
630          } else {
631            this.htmlDescription = null;
632          }
633          return this;
634        }
635    
636        public NewRule setMarkdownDescription(@Nullable String s) {
637          if (htmlDescription != null) {
638            throw new IllegalStateException(String.format("Rule '%s' already has an HTML description", this));
639          }
640          this.markdownDescription = StringUtils.trimToNull(s);
641          return this;
642        }
643    
644        /**
645         * Load description from a file available in classpath. Example : <code>setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")</code>
646         */
647        public NewRule setMarkdownDescription(@Nullable URL classpathUrl) {
648          if (classpathUrl != null) {
649            try {
650              setMarkdownDescription(IOUtils.toString(classpathUrl));
651            } catch (IOException e) {
652              throw new IllegalStateException("Fail to read: " + classpathUrl, e);
653            }
654          } else {
655            this.markdownDescription = null;
656          }
657          return this;
658        }
659    
660        /**
661         * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
662         * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
663         * {@link java.lang.IllegalArgumentException}.
664         */
665        public NewRule setStatus(RuleStatus status) {
666          if (status.equals(RuleStatus.REMOVED)) {
667            throw new IllegalArgumentException(String.format("Status 'REMOVED' is not accepted on rule '%s'", this));
668          }
669          this.status = status;
670          return this;
671        }
672    
673        /**
674         * SQALE sub-characteristic. See http://www.sqale.org
675         *
676         * @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values
677         */
678        public NewRule setDebtSubCharacteristic(@Nullable String s) {
679          this.debtSubCharacteristic = s;
680          return this;
681        }
682    
683        /**
684         * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
685         */
686        public DebtRemediationFunctions debtRemediationFunctions() {
687          return functions;
688        }
689    
690        /**
691         * @see #debtRemediationFunctions()
692         */
693        public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
694          this.debtRemediationFunction = fn;
695          return this;
696        }
697    
698        /**
699         * For rules that use "Linear"/"Linear with offset" remediation functions, the meaning
700         * of the function parameter (= "effort to fix") must be set. This description
701         * explains what 1 point of "effort to fix" represents for the rule.
702         * <p/>
703         * Example : : for the "Insufficient condition coverage", this description for the
704         * remediation function coefficient/offset would be something like
705         * "Effort to test one uncovered condition".
706         */
707        public NewRule setEffortToFixDescription(@Nullable String s) {
708          this.effortToFixDescription = s;
709          return this;
710        }
711    
712        public NewParam createParam(String paramKey) {
713          if (paramsByKey.containsKey(paramKey)) {
714            throw new IllegalArgumentException(String.format("The parameter '%s' is declared several times on the rule %s", paramKey, this));
715          }
716          NewParam param = new NewParam(paramKey);
717          paramsByKey.put(paramKey, param);
718          return param;
719        }
720    
721        @CheckForNull
722        public NewParam param(String paramKey) {
723          return paramsByKey.get(paramKey);
724        }
725    
726        public Collection<NewParam> params() {
727          return paramsByKey.values();
728        }
729    
730        /**
731         * @see RuleTagFormat
732         */
733        public NewRule addTags(String... list) {
734          for (String tag : list) {
735            RuleTagFormat.validate(tag);
736            tags.add(tag);
737          }
738          return this;
739        }
740    
741        /**
742         * @see RuleTagFormat
743         */
744        public NewRule setTags(String... list) {
745          tags.clear();
746          addTags(list);
747          return this;
748        }
749    
750        /**
751         * Optional key that can be used by the rule engine. Not displayed
752         * in webapp. For example the Java Checkstyle plugin feeds this field
753         * with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
754         */
755        public NewRule setInternalKey(@Nullable String s) {
756          this.internalKey = s;
757          return this;
758        }
759    
760        private void validate() {
761          if (Strings.isNullOrEmpty(name)) {
762            throw new IllegalStateException(String.format("Name of rule %s is empty", this));
763          }
764          if (Strings.isNullOrEmpty(htmlDescription) && Strings.isNullOrEmpty(markdownDescription)) {
765            throw new IllegalStateException(String.format("One of HTML description or Markdown description must be defined for rule %s", this));
766          }
767          if ((Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction != null) || (!Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction == null)) {
768            throw new IllegalStateException(String.format("Both debt sub-characteristic and debt remediation function should be defined on rule '%s'", this));
769          }
770        }
771    
772        @Override
773        public String toString() {
774          return String.format("[repository=%s, key=%s]", repoKey, key);
775        }
776      }
777    
778      @Immutable
779      class Rule {
780        private final Repository repository;
781        private final String repoKey, key, name, htmlDescription, markdownDescription, internalKey, severity;
782        private final boolean template;
783        private final String debtSubCharacteristic;
784        private final DebtRemediationFunction debtRemediationFunction;
785        private final String effortToFixDescription;
786        private final Set<String> tags;
787        private final Map<String, Param> params;
788        private final RuleStatus status;
789    
790        private Rule(Repository repository, NewRule newRule) {
791          this.repository = repository;
792          this.repoKey = newRule.repoKey;
793          this.key = newRule.key;
794          this.name = newRule.name;
795          this.htmlDescription = newRule.htmlDescription;
796          this.markdownDescription = newRule.markdownDescription;
797          this.internalKey = newRule.internalKey;
798          this.severity = newRule.severity;
799          this.template = newRule.template;
800          this.status = newRule.status;
801          this.debtSubCharacteristic = newRule.debtSubCharacteristic;
802          this.debtRemediationFunction = newRule.debtRemediationFunction;
803          this.effortToFixDescription = newRule.effortToFixDescription;
804          this.tags = ImmutableSortedSet.copyOf(newRule.tags);
805          ImmutableMap.Builder<String, Param> paramsBuilder = ImmutableMap.builder();
806          for (NewParam newParam : newRule.paramsByKey.values()) {
807            paramsBuilder.put(newParam.key, new Param(newParam));
808          }
809          this.params = paramsBuilder.build();
810        }
811    
812        public Repository repository() {
813          return repository;
814        }
815    
816        public String key() {
817          return key;
818        }
819    
820        public String name() {
821          return name;
822        }
823    
824        public String severity() {
825          return severity;
826        }
827    
828        @CheckForNull
829        public String htmlDescription() {
830          return htmlDescription;
831        }
832    
833        @CheckForNull
834        public String markdownDescription() {
835          return markdownDescription;
836        }
837    
838        public boolean template() {
839          return template;
840        }
841    
842        public RuleStatus status() {
843          return status;
844        }
845    
846        @CheckForNull
847        public String debtSubCharacteristic() {
848          return debtSubCharacteristic;
849        }
850    
851        @CheckForNull
852        public DebtRemediationFunction debtRemediationFunction() {
853          return debtRemediationFunction;
854        }
855    
856        @CheckForNull
857        public String effortToFixDescription() {
858          return effortToFixDescription;
859        }
860    
861        @CheckForNull
862        public Param param(String key) {
863          return params.get(key);
864        }
865    
866        public List<Param> params() {
867          return ImmutableList.copyOf(params.values());
868        }
869    
870        public Set<String> tags() {
871          return tags;
872        }
873    
874        /**
875         * @see RulesDefinition.NewRule#setInternalKey(String)
876         */
877        @CheckForNull
878        public String internalKey() {
879          return internalKey;
880        }
881    
882        @Override
883        public boolean equals(Object o) {
884          if (this == o) {
885            return true;
886          }
887          if (o == null || getClass() != o.getClass()) {
888            return false;
889          }
890          Rule other = (Rule) o;
891          return key.equals(other.key) && repoKey.equals(other.repoKey);
892        }
893    
894        @Override
895        public int hashCode() {
896          int result = repoKey.hashCode();
897          result = 31 * result + key.hashCode();
898          return result;
899        }
900    
901        @Override
902        public String toString() {
903          return String.format("[repository=%s, key=%s]", repoKey, key);
904        }
905      }
906    
907      class NewParam {
908        private final String key;
909        private String name, description, defaultValue;
910        private RuleParamType type = RuleParamType.STRING;
911    
912        private NewParam(String key) {
913          this.key = this.name = key;
914        }
915    
916        public String key() {
917          return key;
918        }
919    
920        public NewParam setName(@Nullable String s) {
921          // name must never be null.
922          this.name = StringUtils.defaultIfBlank(s, key);
923          return this;
924        }
925    
926        public NewParam setType(RuleParamType t) {
927          this.type = t;
928          return this;
929        }
930    
931        /**
932         * Plain-text description. Can be null.
933         */
934        public NewParam setDescription(@Nullable String s) {
935          this.description = StringUtils.defaultIfBlank(s, null);
936          return this;
937        }
938    
939        /**
940         * Empty default value will be converted to null.
941         */
942        public NewParam setDefaultValue(@Nullable String s) {
943          this.defaultValue = Strings.emptyToNull(s);
944          return this;
945        }
946      }
947    
948      @Immutable
949      class Param {
950        private final String key, name, description, defaultValue;
951        private final RuleParamType type;
952    
953        private Param(NewParam newParam) {
954          this.key = newParam.key;
955          this.name = newParam.name;
956          this.description = newParam.description;
957          this.defaultValue = newParam.defaultValue;
958          this.type = newParam.type;
959        }
960    
961        public String key() {
962          return key;
963        }
964    
965        public String name() {
966          return name;
967        }
968    
969        @Nullable
970        public String description() {
971          return description;
972        }
973    
974        @Nullable
975        public String defaultValue() {
976          return defaultValue;
977        }
978    
979        public RuleParamType type() {
980          return type;
981        }
982    
983        @Override
984        public boolean equals(Object o) {
985          if (this == o) {
986            return true;
987          }
988          if (o == null || getClass() != o.getClass()) {
989            return false;
990          }
991          Param that = (Param) o;
992          return key.equals(that.key);
993        }
994    
995        @Override
996        public int hashCode() {
997          return key.hashCode();
998        }
999      }
1000    
1001      /**
1002       * This method is executed when server is started.
1003       */
1004      void define(Context context);
1005    
1006    }