All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.pmd.properties.PropertyBuilder Maven / Gradle / Ivy

Go to download

PMD is an extensible multilanguage static code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It's mainly concerned with Java and Apex, but supports 16 other languages. It comes with 400+ built-in rules. It can be extended with custom rules. It uses JavaCC and Antlr to parse source files into abstract syntax trees (AST) and runs rules against them to find violations. Rules can be written in Java or using a XPath query. Currently, PMD supports Java, JavaScript, Salesforce.com Apex and Visualforce, Kotlin, Swift, Modelica, PLSQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL. Scala is supported, but there are currently no Scala rules available. Additionally, it includes CPD, the copy-paste-detector. CPD finds duplicated code in Coco, C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Julia, Kotlin, Lua, Matlab, Modelica, Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and Visualforce, Scala, Swift, T-SQL, Typescript, Apache Velocity, WSDL, XML and XSL.

There is a newer version: 7.5.0-metrics
Show newest version
/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.properties;

import static java.util.Collections.emptyList;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import net.sourceforge.pmd.properties.internal.PropertyParsingUtil;
import net.sourceforge.pmd.properties.internal.PropertyTypeId;
import net.sourceforge.pmd.util.AssertionUtil;
import net.sourceforge.pmd.util.CollectionUtil;
import net.sourceforge.pmd.util.IteratorUtil;

// @formatter:off
/**
 * Base class for generic property builders.
 * Property builders are obtained from the {@link PropertyFactory},
 * and are used to build {@link PropertyDescriptor}s.
 *
 * 

All properties must specify the following attributes to build * properly: *

    *
  • A name: filled-in when obtaining the builder *
  • A description: see {@link #desc(String)} *
  • A default value: see {@link #defaultValue(Object)} *
* *

The {@link PropertyDescriptor} may be built after those required steps by * calling {@link #build()}. * *

A property builder may throw {@link IllegalArgumentException} at any * stage during the build process to indicate invalid input. It usually tries * to do so as early as possible, rather than waiting for the call to {@link #build()}. * * @param Concrete type of this builder instance * @param Type of values the property handles * * @author Clément Fournier * @since 6.10.0 */ // @formatter:on public abstract class PropertyBuilder, T> { private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z][\\w-]*"); private final String name; private String description; private T defaultValue; /** * Non-null if declared in XML. */ protected @Nullable PropertyTypeId typeId; protected boolean isXPathAvailable = false; PropertyBuilder(String name) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("Name must be provided"); } else if (!NAME_PATTERN.matcher(name).matches()) { throw new IllegalArgumentException("Invalid name '" + name + "'"); } this.name = name; } String getDescription() { if (!isDescriptionSet()) { throw new IllegalArgumentException("Description must be provided"); } return description; } boolean isDescriptionSet() { return StringUtils.isNotBlank(description); } /** Returns the value, asserting it has been set. */ T getDefaultValue() { if (!isDefaultValueSet()) { throw new IllegalArgumentException("A default value must be provided"); } return defaultValue; } boolean isDefaultValueSet() { return defaultValue != null; } /** * Specify the description of the property. This is used for documentation. * Please describe precisely how the property may change the behaviour of the * rule. Providing complete information should be preferred over being concise. * *

Calling this method is required for {@link #build()} to succeed. * * @param desc The description * * @return The same builder * * @throws IllegalArgumentException If the description is null or whitespace */ @SuppressWarnings("unchecked") public B desc(String desc) { if (StringUtils.isBlank(desc)) { throw new IllegalArgumentException("Description must be provided"); } this.description = desc; return (B) this; } @SuppressWarnings("unchecked") B typeId(PropertyTypeId typeId) { this.typeId = typeId; return (B) this; } /** * If true, the property will be made available to XPath queries as * an XPath variable. The default is false (except for properties * of XPath rules that were defined in XML). * * @param b Whether to enable or not * * @return This builder */ public B availableInXPath(boolean b) { this.isXPathAvailable = b; return (B) this; } /** * Add a constraint on the values that this property may take. * The validity of values will be checked when parsing the XML, * and invalid values will be reported. A rule will never be run * if some of its properties violate some constraints. * *

Constraints should be independent from each other, and should * perform no side effects. PMD doesn't specify how many times a * constraint predicate will be executed, or in what order. * * @param constraint The constraint * * @return The same builder * * @see NumericConstraints */ @SuppressWarnings("unchecked") public abstract B require(PropertyConstraint constraint); /** * Specify a default value. Some subclasses provide convenient * related methods, see e.g. {@link GenericCollectionPropertyBuilder#defaultValues(Object, Object[])}. * Using the null value is prohibited. * *

Calling this method is required for {@link #build()} to succeed. * * @param val Default value * * @return The same builder * * @throws IllegalArgumentException If the argument is null */ @SuppressWarnings("unchecked") public B defaultValue(@NonNull T val) { //noinspection ConstantConditions if (val == null) { throw new IllegalArgumentException("Property values may not be null."); } this.defaultValue = val; return (B) this; } /** * Builds the descriptor and returns it. * * @return The built descriptor * * @throws IllegalArgumentException if the description or default value were not provided * @throws IllegalArgumentException if the default value does not satisfy the given constraints */ public abstract PropertyDescriptor build(); /** * Returns the name of the property to be built. */ public String getName() { return name; } // Technically this may very well be merged into PropertyBuilder // We'd have all properties (even collection properties) enjoy a ValueParser, // which means they could be parsed in a tag (collections would use delimiters) if they opt in. // The delimiters wouldn't be configurable (we'd take our current defaults). If they could ambiguous // then the syntax should be the only one available. // This would allow specifying eg lists of numbers as 1,2,3, for which the syntax would look clumsy abstract static class BaseSinglePropertyBuilder, T> extends PropertyBuilder { private PropertySerializer parser; // Class is not final but a package-private constructor restricts inheritance BaseSinglePropertyBuilder(String name, PropertySerializer parser) { super(name); this.parser = parser; } protected PropertySerializer getParser() { return parser; } @SuppressWarnings("unchecked") @Override public B require(PropertyConstraint constraint) { parser = parser.withConstraint(constraint); return (B) this; } /** * Returns a new builder that can be used to build a property * handling lists of Ts. The validators already added are * converted to list validators. The default value cannot have * previously been set. * * @return A new list property builder * * @throws IllegalStateException if the default value has already been set * * @see #map(Collector) */ public GenericCollectionPropertyBuilder> toList() { return map(Collectors.toList()); } /** * Returns a new builder that can be used to build a property * with value type {@code }. The validators already added are * converted to collection validators. The default value cannot * have previously been set. The returned builder will support * conversion to and from a delimited string. * *

Example usage: *

{@code
         * // this can be set with
         * // a,b,c
         * PropertyDescriptor> whitelistSet =
         *      PropertyFactory.stringProperty("whitelist")
         *                     .desc(...)
         *                     .map(Collectors.toSet())
         *                     .emptyDefaultValue()
         *                     .build();
         * }
* * @return A new list property builder * * @throws IllegalStateException if the default value has already been set */ public > GenericCollectionPropertyBuilder map(Collector collector) { if (isDefaultValueSet()) { throw new IllegalStateException("The default value is already set!"); } GenericCollectionPropertyBuilder result = new GenericCollectionPropertyBuilder<>(getName(), getParser(), collector); if (isDescriptionSet()) { result.desc(getDescription()); } return result; } /** * Returns a new builder that can be used to build a property * handling {@code Optional}. The validators already added * are used on the validator property. If the default value was * previously set, it is converted to an optional with {@link Optional#of(Object)}. * * @param missingValue The string representation of the empty optional. * * @return A new property builder for an optional. */ public GenericPropertyBuilder> toOptional(String missingValue) { AssertionUtil.requireParamNotNull("missingValue", missingValue); PropertySerializer> serializer = PropertyParsingUtil.toOptional(getParser(), missingValue); GenericPropertyBuilder> result = new GenericPropertyBuilder<>(this.getName(), serializer); if (isDefaultValueSet()) { result.defaultValue(Optional.of(getDefaultValue())); } if (isDescriptionSet()) { result.desc(getDescription()); } return result; } @Override public PropertyDescriptor build() { return new PropertyDescriptor<>( getName(), getDescription(), getDefaultValue(), parser, typeId, isXPathAvailable); } } /** * Generic builder for a single-value property. * * @param Type of values the property handles * * @author Clément Fournier * @since 6.10.0 */ // Note: This type is used to fix the first type parameter for classes that don't need more API. public static class GenericPropertyBuilder extends BaseSinglePropertyBuilder, T> { GenericPropertyBuilder(String name, PropertySerializer parser) { super(name, parser); } } /** * Specialized builder for regex properties. Allows specifying the pattern as a * string, with {@link #defaultValue(String)}. * * @author Clément Fournier * @since 6.10.0 */ public static final class RegexPropertyBuilder extends BaseSinglePropertyBuilder { RegexPropertyBuilder(String name) { super(name, PropertyParsingUtil.REGEX); } /** * Specify a default value using a string pattern. The argument is * compiled to a pattern using {@link Pattern#compile(String)}. * * @param pattern String pattern * * @return The same builder * * @throws java.util.regex.PatternSyntaxException If the argument is not a valid pattern */ public RegexPropertyBuilder defaultValue(String pattern) { super.defaultValue(Pattern.compile(pattern)); return this; } /** * Specify a default value using a string pattern. The argument is * compiled to a pattern using {@link Pattern#compile(String, int)}. * * @param pattern String pattern * @param flags Regex compilation flags, the same as for {@link Pattern#compile(String, int)} * * @return The same builder * * @throws java.util.regex.PatternSyntaxException If the argument is not a valid pattern * @throws IllegalArgumentException If bit values other than those corresponding to the defined * match flags are set in {@code flags} */ public RegexPropertyBuilder defaultValue(String pattern, int flags) { super.defaultValue(Pattern.compile(pattern, flags)); return this; } } /** * Generic builder for a collection-valued property. * This class adds methods related to {@link #defaultValue(Iterable)} * to make its use more flexible. See e.g. {@link #defaultValues(Object, Object[])}. * *

Note: this is designed to support arbitrary collections. * Pre-7.0.0, the only collections available from the {@link PropertyFactory} * are list types though. * * @param Component type of the collection * @param Collection type for the property being built * * @author Clément Fournier * @since 6.10.0 */ public static final class GenericCollectionPropertyBuilder> extends PropertyBuilder, C> { private PropertySerializer itemParser; private final Collector collector; private final List> collectionConstraints = new ArrayList<>(); /** * Builds a new builder for a collection type. Package-private. */ GenericCollectionPropertyBuilder(String name, PropertySerializer itemParser, Collector collector) { super(name); this.itemParser = itemParser; this.collector = collector; } private C getDefaultValue(Iterable list) { return IteratorUtil.toStream(list).collect(collector); } @Override public GenericCollectionPropertyBuilder require(PropertyConstraint constraint) { collectionConstraints.add(constraint); return this; } /** * Specify a default value. This will be converted to type * {@code } with the supplied collector. * * @param val List of values * * @return The same builder */ public GenericCollectionPropertyBuilder defaultValue(Iterable val) { super.defaultValue(getDefaultValue(val)); return this; } /** * Specify default values. To specify an empty * default value, use {@link #emptyDefaultValue()}. * * @param head First value * @param tail Rest of the values * * @return The same builder */ @SuppressWarnings("unchecked") public GenericCollectionPropertyBuilder defaultValues(V head, V... tail) { return this.defaultValue(listOf(head, tail)); } /** * Specify that the default value is an empty collection. * * @return The same builder */ public GenericCollectionPropertyBuilder emptyDefaultValue() { return this.defaultValue(emptyList()); } /** * Require that the given constraint be fulfilled on each item of the * value of this property. This is a convenient shorthand for * {@code require(constraint.toCollectionConstraint())}. * * @param constraint Constraint to impose on the items of the collection value * * @return The same builder */ public GenericCollectionPropertyBuilder requireEach(PropertyConstraint constraint) { this.itemParser = itemParser.withConstraint(constraint); return this; } @Override public PropertyDescriptor build() { PropertySerializer syntax = PropertyParsingUtil.delimitedString(itemParser, collector); syntax = PropertyParsingUtil.withAllConstraints(syntax, CollectionUtil.map(itemParser.getConstraints(), PropertyConstraint::toCollectionConstraint)); syntax = PropertyParsingUtil.withAllConstraints(syntax, collectionConstraints); return new PropertyDescriptor<>( getName(), getDescription(), getDefaultValue(), syntax, typeId, isXPathAvailable); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy