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 }