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