001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.test.valueobjects.property.impl;
017
018import static de.cuioss.tools.string.MoreStrings.emptyToNull;
019import static de.cuioss.tools.string.MoreStrings.isEmpty;
020import static java.util.Objects.requireNonNull;
021
022import de.cuioss.test.valueobjects.property.PropertyMetadata;
023import de.cuioss.test.valueobjects.property.util.PropertyAccessStrategy;
024import lombok.AccessLevel;
025import lombok.EqualsAndHashCode;
026import lombok.Getter;
027import lombok.RequiredArgsConstructor;
028import lombok.ToString;
029import lombok.experimental.Delegate;
030
031/**
032 * Extensions of {@link PropertyMetadata} that deals with builder-specific
033 * aspects
034 *
035 * @author Oliver Wolff
036 */
037@EqualsAndHashCode
038@ToString
039@RequiredArgsConstructor(access = AccessLevel.MODULE)
040public class BuilderMetadata implements PropertyMetadata {
041
042    @Delegate
043    private final PropertyMetadata delegate;
044
045    /**
046     * Used for builder testing / verifying: In case builderMethodPrefix is null the
047     * corresponding build method to be accessed for setting the value is the name
048     * attribute: {@link PropertyMetadata#getName()}, in case it is a concrete
049     * value, e.g. 'with' it will taken into account: withPropertName().
050     */
051    @Getter
052    private final String builderAddMethodName;
053
054    /**
055     * Similar to {@link BuilderMetadataBuilder#builderMethodName(String)} but for
056     * special cases described within
057     * {@link PropertyAccessStrategy#BUILDER_COLLECTION_AND_SINGLE_ELEMENT} There
058     * are two different steps for deriving the concrete method-name:
059     * <ul>
060     * <li>Base-name: In case
061     * {@link BuilderMetadataBuilder#builderSingleAddMethodName(String)} is not set
062     * it used {@link PropertyMetadata#getName()} as base name -> Overloading</li>
063     * <li>The base-name will prefixed if there is a builderPerfixed configured, see
064     * {@link BuilderMetadataBuilder#builderMethodPrefix(String)}</li>
065     * </ul>
066     */
067    @Getter
068    private final String builderSingleAddMethodName;
069
070    static String prefixBuilderMethod(final String nameToBePrefixed, final String builderMethodPrefix) {
071        requireNonNull(emptyToNull(nameToBePrefixed), "nameToBePrefixed");
072        if (isEmpty(builderMethodPrefix)) {
073            return nameToBePrefixed;
074        }
075        final var builder = new StringBuilder(builderMethodPrefix);
076        builder.append(Character.toUpperCase(nameToBePrefixed.charAt(0)));
077        builder.append(nameToBePrefixed.substring(1));
078        return builder.toString();
079    }
080
081    /**
082     * Builder for instances of {@link BuilderMetadata}
083     *
084     * @author Oliver Wolff
085     */
086    public static class BuilderMetadataBuilder {
087
088        private String tempBuilderMethodName;
089        private String tempBuilderMethodPrefix;
090        private String tempBuilderSingleAddMethodName;
091        private PropertyMetadata tempDelegate;
092
093        /**
094         * In case this methodName is set it will be used directly for deriving the
095         * write-method. {@link #builderMethodPrefix(String)} will then ignored
096         *
097         * @param builderMethodName to be set
098         * @return the builder for {@link BuilderMetadata}
099         */
100        public BuilderMetadataBuilder builderMethodName(final String builderMethodName) {
101            tempBuilderMethodName = builderMethodName;
102            return this;
103        }
104
105        /**
106         * Defines a delegate {@link PropertyMetadata} used for all attributes that are
107         * not builder specific, required.
108         *
109         * @param delegate to be set
110         * @return the builder for {@link BuilderMetadata}
111         */
112        public BuilderMetadataBuilder delegateMetadata(final PropertyMetadata delegate) {
113            tempDelegate = delegate;
114            return this;
115        }
116
117        /**
118         * see {@link BuilderMetadata#getBuilderAddMethodName()} for details
119         *
120         * @param builderMethodPrefix to be set
121         * @return the builder for {@link BuilderMetadata}
122         */
123        public BuilderMetadataBuilder builderMethodPrefix(final String builderMethodPrefix) {
124            tempBuilderMethodPrefix = emptyToNull(builderMethodPrefix);
125            return this;
126        }
127
128        /**
129         * Only needed for builder that deal with {@link Iterable} and single elements,
130         * see {@link PropertyAccessStrategy#BUILDER_COLLECTION_AND_SINGLE_ELEMENT} for
131         * details
132         *
133         * @param builderSingleAddMethodName to be set
134         * @return the builder for {@link BuilderMetadata}
135         */
136        public BuilderMetadataBuilder builderSingleAddMethodName(final String builderSingleAddMethodName) {
137            tempBuilderSingleAddMethodName = emptyToNull(builderSingleAddMethodName);
138            return this;
139        }
140
141        /**
142         * @return the built {@link BuilderMetadata}
143         */
144        public BuilderMetadata build() {
145            requireNonNull(tempDelegate, "delegate");
146
147            final var tempPropertyName = tempDelegate.getName();
148
149            var addMethodName = tempBuilderMethodName;
150            if (isEmpty(addMethodName)) {
151                addMethodName = prefixBuilderMethod(tempPropertyName, tempBuilderMethodPrefix);
152            }
153
154            var singleAddMethodName = tempBuilderSingleAddMethodName;
155            if (isEmpty(singleAddMethodName)) {
156                singleAddMethodName = prefixBuilderMethod(tempPropertyName, tempBuilderMethodPrefix);
157            }
158
159            return new BuilderMetadata(tempDelegate, addMethodName, singleAddMethodName);
160        }
161    }
162
163    /**
164     * @return a new instance of {@link BuilderMetadataBuilder}
165     */
166    public static BuilderMetadataBuilder builder() {
167        return new BuilderMetadataBuilder();
168    }
169
170    /**
171     * Simple converter for creating a {@link BuilderMetadata} from any given
172     * {@link PropertyMetadata} with defaults for the methods.
173     *
174     * @param metadata must not be null
175     * @return a {@link BuilderMetadata} computed from the given
176     *         {@link PropertyMetadata} without further configuration
177     */
178    public static BuilderMetadata wrapFromMetadata(final PropertyMetadata metadata) {
179        return builder().delegateMetadata(metadata).build();
180    }
181}