@NonNull
    public Set<Attribute<T, ?>> describe() {

        Map<String, Attribute<T, ?>> attributes = new HashMap<>();
        final Set<String> exclusions = exclusions();

        for (Field field : getTarget().getFields()) {
            final String name = field.getName();
            if (exclusions.contains(name)) {
                continue;
            }

            if (PersistedList.class.isAssignableFrom(field.getType())) {
                if (Modifier.isTransient(field.getModifiers())) {
                    exclusions.add(name);
                    continue;
                }

                Attribute attribute = createAttribute(name, TypePair.of(field))
                        .getter(field::get); // get value by direct access to public final field
                attributes.put(name, attribute);
            }
        }

        final Class<T> target = getTarget();
        // Resolve the methods and merging overrides to more concretized signatures
        // because the methods can to have been overridden with concretized type
        // TODO: Overloaded setters with different types can corrupt this logic
        for (Method method : target.getMethods()) {
            final String methodName = method.getName();
            TypePair type;
            if (method.getParameterCount() == 0
                    && methodName.startsWith("get")
                    && PersistedList.class.isAssignableFrom(method.getReturnType())) {
                type = TypePair.ofReturnType(method);
            } else if (method.getParameterCount() != 1 || !methodName.startsWith("set")) {
                // Not an accessor, ignore
                continue;
            } else {
                type = TypePair.ofParameter(method, 0);
            }

            final String s = methodName.substring(3);
            final String name = StringUtils.uncapitalize(s);
            if (exclusions.contains(name)) {
                continue;
            }

            if (!hasGetter(target, s)) {
                // Looks like a property but no actual getter method we can use to read value
                continue;
            }

            LOGGER.log(Level.FINER, "Processing {0} property", name);

            if (Map.class.isAssignableFrom(type.rawType)) {
                // yaml has support for Maps, but as nobody seem to like them we agreed not to support them
                LOGGER.log(Level.FINER, "{0} is a Map<?,?>. We decided not to support Maps.", name);
                continue;
            }

            Attribute attribute = createAttribute(name, type);
            if (attribute == null) {
                continue;
            }

            attribute.deprecated(method.getAnnotation(Deprecated.class) != null);
            final Restricted r = method.getAnnotation(Restricted.class);
            if (r != null) {
                attribute.restrictions(r.value());
            }

            Attribute prevAttribute = attributes.get(name);
            // Replace the method if it have more concretized type
            if (prevAttribute == null || prevAttribute.type.isAssignableFrom(attribute.type)) {
                attributes.put(name, attribute);
            }
        }

        return new HashSet<>(attributes.values());
    }
a General purpose abstract {@link Configurator} implementation based on introspection.
Target component is identified by implementing {@link #instance(Mapping, ConfigurationContext)} then configuration is applied on
{@link Attribute}s as defined by {@link #describe()}.
This base implementation uses JavaBean convention to identify configurable attributes.

@author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
plugin/src/main/java/io/jenkins/plugins/casc/BaseConfigurator.java