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 the coding rules. 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"));
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        private SubCharacteristics() {
297          // only constants
298        }
299      }
300    
301      /**
302       * Instantiated by core but not by plugins
303       */
304      class Context {
305        private final Map<String, Repository> repositoriesByKey = Maps.newHashMap();
306        private final ListMultimap<String, ExtendedRepository> extendedRepositoriesByKey = ArrayListMultimap.create();
307    
308    
309        public NewRepository createRepository(String key, String language) {
310          return new NewRepositoryImpl(this, key, language, false);
311        }
312    
313        public NewExtendedRepository extendRepository(String key, String language) {
314          return new NewRepositoryImpl(this, key, language, true);
315        }
316    
317        @CheckForNull
318        public Repository repository(String key) {
319          return repositoriesByKey.get(key);
320        }
321    
322        public List<Repository> repositories() {
323          return ImmutableList.copyOf(repositoriesByKey.values());
324        }
325    
326        public List<ExtendedRepository> extendedRepositories(String repositoryKey) {
327          return ImmutableList.copyOf(extendedRepositoriesByKey.get(repositoryKey));
328        }
329    
330        public List<ExtendedRepository> extendedRepositories() {
331          return ImmutableList.copyOf(extendedRepositoriesByKey.values());
332        }
333    
334        private void registerRepository(NewRepositoryImpl newRepository) {
335          if (repositoriesByKey.containsKey(newRepository.key)) {
336            throw new IllegalStateException(String.format("The rule repository '%s' is defined several times", newRepository.key));
337          }
338          repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
339        }
340    
341        private void registerExtendedRepository(NewRepositoryImpl newRepository) {
342          extendedRepositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
343        }
344      }
345    
346      interface NewExtendedRepository {
347        NewRule createRule(String ruleKey);
348    
349        @CheckForNull
350        NewRule rule(String ruleKey);
351    
352        Collection<NewRule> rules();
353    
354        String key();
355    
356        void done();
357      }
358    
359      interface NewRepository extends NewExtendedRepository {
360        NewRepository setName(String s);
361      }
362    
363      class NewRepositoryImpl implements NewRepository {
364        private final Context context;
365        private final boolean extended;
366        private final String key;
367        private String language;
368        private String name;
369        private final Map<String, NewRule> newRules = Maps.newHashMap();
370    
371        private NewRepositoryImpl(Context context, String key, String language, boolean extended) {
372          this.extended = extended;
373          this.context = context;
374          this.key = this.name = key;
375          this.language = language;
376        }
377    
378        @Override
379        public String key() {
380          return key;
381        }
382    
383        @Override
384        public NewRepositoryImpl setName(@Nullable String s) {
385          if (StringUtils.isNotEmpty(s)) {
386            this.name = s;
387          }
388          return this;
389        }
390    
391        @Override
392        public NewRule createRule(String ruleKey) {
393          if (newRules.containsKey(ruleKey)) {
394            // Should fail in a perfect world, but at the time being the Findbugs plugin
395            // defines several times the rule EC_INCOMPATIBLE_ARRAY_COMPARE
396            // See http://jira.codehaus.org/browse/SONARJAVA-428
397            LoggerFactory.getLogger(getClass()).warn(String.format("The rule '%s' of repository '%s' is declared several times", ruleKey, key));
398          }
399          NewRule newRule = new NewRule(key, ruleKey);
400          newRules.put(ruleKey, newRule);
401          return newRule;
402        }
403    
404        @CheckForNull
405        @Override
406        public NewRule rule(String ruleKey) {
407          return newRules.get(ruleKey);
408        }
409    
410        @Override
411        public Collection<NewRule> rules() {
412          return newRules.values();
413        }
414    
415        @Override
416        public void done() {
417          // note that some validations can be done here, for example for
418          // verifying that at least one rule is declared
419    
420          if (extended) {
421            context.registerExtendedRepository(this);
422          } else {
423            context.registerRepository(this);
424          }
425        }
426      }
427    
428      interface ExtendedRepository {
429        String key();
430    
431        String language();
432    
433        @CheckForNull
434        Rule rule(String ruleKey);
435    
436        List<Rule> rules();
437      }
438    
439      interface Repository extends ExtendedRepository {
440        String name();
441      }
442    
443      @Immutable
444      class RepositoryImpl implements Repository {
445        private final String key, language, name;
446        private final Map<String, Rule> rulesByKey;
447    
448        private RepositoryImpl(NewRepositoryImpl newRepository) {
449          this.key = newRepository.key;
450          this.language = newRepository.language;
451          this.name = newRepository.name;
452          ImmutableMap.Builder<String, Rule> ruleBuilder = ImmutableMap.builder();
453          for (NewRule newRule : newRepository.newRules.values()) {
454            newRule.validate();
455            ruleBuilder.put(newRule.key, new Rule(this, newRule));
456          }
457          this.rulesByKey = ruleBuilder.build();
458        }
459    
460        @Override
461        public String key() {
462          return key;
463        }
464    
465        @Override
466        public String language() {
467          return language;
468        }
469    
470        @Override
471        public String name() {
472          return name;
473        }
474    
475        @Override
476        @CheckForNull
477        public Rule rule(String ruleKey) {
478          return rulesByKey.get(ruleKey);
479        }
480    
481        @Override
482        public List<Rule> rules() {
483          return ImmutableList.copyOf(rulesByKey.values());
484        }
485    
486        @Override
487        public boolean equals(Object o) {
488          if (this == o) {
489            return true;
490          }
491          if (o == null || getClass() != o.getClass()) {
492            return false;
493          }
494          RepositoryImpl that = (RepositoryImpl) o;
495          return key.equals(that.key);
496        }
497    
498        @Override
499        public int hashCode() {
500          return key.hashCode();
501        }
502      }
503    
504      /**
505       * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
506       */
507      interface DebtRemediationFunctions {
508        DebtRemediationFunction linear(String coefficient);
509    
510        DebtRemediationFunction linearWithOffset(String coefficient, String offset);
511    
512        DebtRemediationFunction constantPerIssue(String offset);
513      }
514    
515    
516      class NewRule {
517        private final String repoKey, key;
518        private String name, htmlDescription, internalKey, severity = Severity.MAJOR;
519        private boolean template;
520        private RuleStatus status = RuleStatus.defaultStatus();
521        private String debtSubCharacteristic;
522        private DebtRemediationFunction debtRemediationFunction;
523        private String effortToFixDescription;
524        private final Set<String> tags = Sets.newTreeSet();
525        private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
526        private final DebtRemediationFunctions functions;
527    
528        private NewRule(String repoKey, String key) {
529          this.repoKey = repoKey;
530          this.key = key;
531          this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
532        }
533    
534        public String key() {
535          return this.key;
536        }
537    
538        /**
539         * Required rule name
540         */
541        public NewRule setName(@Nullable String s) {
542          this.name = StringUtils.trimToNull(s);
543          return this;
544        }
545    
546        public NewRule setTemplate(boolean template) {
547          this.template = template;
548          return this;
549        }
550    
551        public NewRule setSeverity(String s) {
552          if (!Severity.ALL.contains(s)) {
553            throw new IllegalArgumentException(String.format("Severity of rule %s is not correct: %s", this, s));
554          }
555          this.severity = s;
556          return this;
557        }
558    
559        public NewRule setHtmlDescription(@Nullable String s) {
560          this.htmlDescription = StringUtils.trimToNull(s);
561          return this;
562        }
563    
564        /**
565         * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code>
566         */
567        public NewRule setHtmlDescription(@Nullable URL classpathUrl) {
568          if (classpathUrl != null) {
569            try {
570              setHtmlDescription(IOUtils.toString(classpathUrl));
571            } catch (IOException e) {
572              throw new IllegalStateException("Fail to read: " + classpathUrl, e);
573            }
574          } else {
575            this.htmlDescription = null;
576          }
577          return this;
578        }
579    
580        /**
581         * Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
582         * {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
583         * {@link java.lang.IllegalArgumentException}.
584         */
585        public NewRule setStatus(RuleStatus status) {
586          if (status.equals(RuleStatus.REMOVED)) {
587            throw new IllegalArgumentException(String.format("Status 'REMOVED' is not accepted on rule '%s'", this));
588          }
589          this.status = status;
590          return this;
591        }
592    
593        /**
594         * SQALE sub-characteristic. See http://www.sqale.org
595         *
596         * @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values
597         */
598        public NewRule setDebtSubCharacteristic(@Nullable String s) {
599          this.debtSubCharacteristic = s;
600          return this;
601        }
602    
603        /**
604         * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
605         */
606        public DebtRemediationFunctions debtRemediationFunctions() {
607          return functions;
608        }
609    
610        /**
611         * @see #debtRemediationFunctions()
612         */
613        public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
614          this.debtRemediationFunction = fn;
615          return this;
616        }
617    
618        /**
619         * For rules that use "Linear"/"Linear with offset" remediation functions, the meaning
620         * of the function parameter (= "effort to fix") must be set. This description
621         * explains what 1 point of "effort to fix" represents for the rule.
622         * <p/>
623         * Example : : for the "Insufficient branch coverage", this description for the
624         * remediation function coefficient/offset would be something like
625         * "Effort to test one uncovered branch".
626         */
627        public NewRule setEffortToFixDescription(@Nullable String s) {
628          this.effortToFixDescription = s;
629          return this;
630        }
631    
632        public NewParam createParam(String paramKey) {
633          if (paramsByKey.containsKey(paramKey)) {
634            throw new IllegalArgumentException(String.format("The parameter '%s' is declared several times on the rule %s", paramKey, this));
635          }
636          NewParam param = new NewParam(paramKey);
637          paramsByKey.put(paramKey, param);
638          return param;
639        }
640    
641        @CheckForNull
642        public NewParam param(String paramKey) {
643          return paramsByKey.get(paramKey);
644        }
645    
646        public Collection<NewParam> params() {
647          return paramsByKey.values();
648        }
649    
650        /**
651         * @see RuleTagFormat
652         */
653        public NewRule addTags(String... list) {
654          for (String tag : list) {
655            RuleTagFormat.validate(tag);
656            tags.add(tag);
657          }
658          return this;
659        }
660    
661        /**
662         * @see RuleTagFormat
663         */
664        public NewRule setTags(String... list) {
665          tags.clear();
666          addTags(list);
667          return this;
668        }
669    
670        /**
671         * Optional key that can be used by the rule engine. Not displayed
672         * in webapp. For example the Java Checkstyle plugin feeds this field
673         * with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
674         */
675        public NewRule setInternalKey(@Nullable String s) {
676          this.internalKey = s;
677          return this;
678        }
679    
680        private void validate() {
681          if (Strings.isNullOrEmpty(name)) {
682            throw new IllegalStateException(String.format("Name of rule %s is empty", this));
683          }
684          if (Strings.isNullOrEmpty(htmlDescription)) {
685            throw new IllegalStateException(String.format("HTML description of rule %s is empty", this));
686          }
687          if ((Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction != null) || (!Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction == null)) {
688            throw new IllegalStateException(String.format("Both debt sub-characteristic and debt remediation function should be defined on rule '%s'", this));
689          }
690        }
691    
692        @Override
693        public String toString() {
694          return String.format("[repository=%s, key=%s]", repoKey, key);
695        }
696      }
697    
698      @Immutable
699      class Rule {
700        private final Repository repository;
701        private final String repoKey, key, name, htmlDescription, internalKey, severity;
702        private final boolean template;
703        private final String debtSubCharacteristic;
704        private final DebtRemediationFunction debtRemediationFunction;
705        private final String effortToFixDescription;
706        private final Set<String> tags;
707        private final Map<String, Param> params;
708        private final RuleStatus status;
709    
710        private Rule(Repository repository, NewRule newRule) {
711          this.repository = repository;
712          this.repoKey = newRule.repoKey;
713          this.key = newRule.key;
714          this.name = newRule.name;
715          this.htmlDescription = newRule.htmlDescription;
716          this.internalKey = newRule.internalKey;
717          this.severity = newRule.severity;
718          this.template = newRule.template;
719          this.status = newRule.status;
720          this.debtSubCharacteristic = newRule.debtSubCharacteristic;
721          this.debtRemediationFunction = newRule.debtRemediationFunction;
722          this.effortToFixDescription = newRule.effortToFixDescription;
723          this.tags = ImmutableSortedSet.copyOf(newRule.tags);
724          ImmutableMap.Builder<String, Param> paramsBuilder = ImmutableMap.builder();
725          for (NewParam newParam : newRule.paramsByKey.values()) {
726            paramsBuilder.put(newParam.key, new Param(newParam));
727          }
728          this.params = paramsBuilder.build();
729        }
730    
731        public Repository repository() {
732          return repository;
733        }
734    
735        public String key() {
736          return key;
737        }
738    
739        public String name() {
740          return name;
741        }
742    
743        public String severity() {
744          return severity;
745        }
746    
747        @CheckForNull
748        public String htmlDescription() {
749          return htmlDescription;
750        }
751    
752        public boolean template() {
753          return template;
754        }
755    
756        public RuleStatus status() {
757          return status;
758        }
759    
760        @CheckForNull
761        public String debtSubCharacteristic() {
762          return debtSubCharacteristic;
763        }
764    
765        @CheckForNull
766        public DebtRemediationFunction debtRemediationFunction() {
767          return debtRemediationFunction;
768        }
769    
770        @CheckForNull
771        public String effortToFixDescription() {
772          return effortToFixDescription;
773        }
774    
775        @CheckForNull
776        public Param param(String key) {
777          return params.get(key);
778        }
779    
780        public List<Param> params() {
781          return ImmutableList.copyOf(params.values());
782        }
783    
784        public Set<String> tags() {
785          return tags;
786        }
787    
788        /**
789         * @see RulesDefinition.NewRule#setInternalKey(String)
790         */
791        @CheckForNull
792        public String internalKey() {
793          return internalKey;
794        }
795    
796        @Override
797        public boolean equals(Object o) {
798          if (this == o) {
799            return true;
800          }
801          if (o == null || getClass() != o.getClass()) {
802            return false;
803          }
804          Rule other = (Rule) o;
805          return key.equals(other.key) && repoKey.equals(other.repoKey);
806        }
807    
808        @Override
809        public int hashCode() {
810          int result = repoKey.hashCode();
811          result = 31 * result + key.hashCode();
812          return result;
813        }
814    
815        @Override
816        public String toString() {
817          return String.format("[repository=%s, key=%s]", repoKey, key);
818        }
819      }
820    
821      class NewParam {
822        private final String key;
823        private String name, description, defaultValue;
824        private RuleParamType type = RuleParamType.STRING;
825    
826        private NewParam(String key) {
827          this.key = this.name = key;
828        }
829    
830        public String key() {
831          return key;
832        }
833    
834        public NewParam setName(@Nullable String s) {
835          // name must never be null.
836          this.name = StringUtils.defaultIfBlank(s, key);
837          return this;
838        }
839    
840        public NewParam setType(RuleParamType t) {
841          this.type = t;
842          return this;
843        }
844    
845        /**
846         * Plain-text description. Can be null.
847         */
848        public NewParam setDescription(@Nullable String s) {
849          this.description = StringUtils.defaultIfBlank(s, null);
850          return this;
851        }
852    
853        public NewParam setDefaultValue(@Nullable String s) {
854          this.defaultValue = s;
855          return this;
856        }
857      }
858    
859      @Immutable
860      class Param {
861        private final String key, name, description, defaultValue;
862        private final RuleParamType type;
863    
864        private Param(NewParam newParam) {
865          this.key = newParam.key;
866          this.name = newParam.name;
867          this.description = newParam.description;
868          this.defaultValue = newParam.defaultValue;
869          this.type = newParam.type;
870        }
871    
872        public String key() {
873          return key;
874        }
875    
876        public String name() {
877          return name;
878        }
879    
880        @Nullable
881        public String description() {
882          return description;
883        }
884    
885        @Nullable
886        public String defaultValue() {
887          return defaultValue;
888        }
889    
890        public RuleParamType type() {
891          return type;
892        }
893    
894        @Override
895        public boolean equals(Object o) {
896          if (this == o) {
897            return true;
898          }
899          if (o == null || getClass() != o.getClass()) {
900            return false;
901          }
902          Param that = (Param) o;
903          return key.equals(that.key);
904        }
905    
906        @Override
907        public int hashCode() {
908          return key.hashCode();
909        }
910      }
911    
912      /**
913       * This method is executed when server is started.
914       */
915      void define(Context context);
916    
917    }