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 java.util.Objects.requireNonNull;
020
021import java.lang.reflect.Array;
022import java.util.ArrayList;
023import java.util.List;
024
025import de.cuioss.test.generator.TypedGenerator;
026import de.cuioss.test.generator.impl.CollectionGenerator;
027import de.cuioss.test.generator.impl.PrimitiveArrayGenerators;
028import de.cuioss.test.valueobjects.property.PropertyMetadata;
029import de.cuioss.test.valueobjects.property.util.AssertionStrategy;
030import de.cuioss.test.valueobjects.property.util.CollectionType;
031import de.cuioss.test.valueobjects.property.util.PropertyAccessStrategy;
032import de.cuioss.tools.property.PropertyMemberInfo;
033import de.cuioss.tools.property.PropertyReadWrite;
034import de.cuioss.tools.string.Joiner;
035import lombok.AccessLevel;
036import lombok.EqualsAndHashCode;
037import lombok.Getter;
038import lombok.RequiredArgsConstructor;
039
040/**
041 * Gathers all information needed for dynamically creating / asserting
042 * properties.
043 *
044 * @author Oliver Wolff
045 */
046@RequiredArgsConstructor(access = AccessLevel.MODULE)
047@EqualsAndHashCode(exclude = "generator", doNotUseGetters = true)
048public class PropertyMetadataImpl implements PropertyMetadata {
049
050    @Getter
051    private final String name;
052
053    @Getter
054    private final TypedGenerator<?> generator;
055
056    @Getter
057    private final boolean defaultValue;
058
059    @Getter
060    private final Class<?> propertyClass;
061
062    private final Class<?> actualClass;
063
064    @Getter
065    private final boolean required;
066
067    @Getter
068    private final PropertyAccessStrategy propertyAccessStrategy;
069
070    @Getter
071    private final CollectionType collectionType;
072
073    @Getter
074    private final PropertyMemberInfo propertyMemberInfo;
075
076    @Getter
077    private final PropertyReadWrite propertyReadWrite;
078
079    @Getter
080    private final AssertionStrategy assertionStrategy;
081
082    @Override
083    public Object next() {
084        switch (collectionType) {
085        case NO_ITERABLE:
086            return generator.next();
087        case ARRAY_MARKER:
088            if (!propertyClass.isPrimitive()) {
089                return resolveCollectionGenerator().list().toArray();
090            }
091            return PrimitiveArrayGenerators.resolveForType(getPropertyClass()).next();
092        default:
093            return collectionType.nextIterable(resolveCollectionGenerator());
094        }
095    }
096
097    @Override
098    public CollectionGenerator<?> resolveCollectionGenerator() {
099        return new CollectionGenerator<>(generator);
100    }
101
102    @Override
103    public Class<?> resolveActualClass() {
104        return actualClass;
105    }
106
107    /**
108     * @author Oliver Wolff
109     */
110    public static class PropertyMetadataBuilder {
111
112        private TypedGenerator<?> tempGenerator;
113        private String tempName;
114        private boolean tempDefaultValue = false;
115        private Class<?> tempPropertyClass;
116        private boolean tempRequired = false;
117        private PropertyMemberInfo tempPropertyMemberInfo = PropertyMemberInfo.DEFAULT;
118        private CollectionType tempCollectionType = CollectionType.NO_ITERABLE;
119        private PropertyAccessStrategy tempPropertyAccessStrategy = PropertyAccessStrategy.BEAN_PROPERTY;
120        private PropertyReadWrite tempPropertyReadWrite = PropertyReadWrite.READ_WRITE;
121        private AssertionStrategy tempAssertionStrategy = AssertionStrategy.DEFAULT;
122
123        /**
124         * @param propertyAccessStrategy to be set
125         * @return the builder for {@link PropertyMetadataImpl}
126         */
127        public PropertyMetadataBuilder propertyAccessStrategy(final PropertyAccessStrategy propertyAccessStrategy) {
128            tempPropertyAccessStrategy = propertyAccessStrategy;
129            return this;
130        }
131
132        /**
133         * @param propertyReadWrite to be set
134         * @return the builder for {@link PropertyMetadataImpl}
135         */
136        public PropertyMetadataBuilder propertyReadWrite(final PropertyReadWrite propertyReadWrite) {
137            tempPropertyReadWrite = propertyReadWrite;
138            return this;
139        }
140
141        /**
142         * @param assertionStrategy to be set
143         * @return the builder for {@link PropertyMetadataImpl}
144         */
145        public PropertyMetadataBuilder assertionStrategy(final AssertionStrategy assertionStrategy) {
146            tempAssertionStrategy = assertionStrategy;
147            return this;
148        }
149
150        /**
151         * In case you have a {@link TypedGenerator} it will be implicitly set as
152         * {@link PropertyMetadataImpl#getGenerator()} and
153         * {@link PropertyMetadataImpl#getPropertyClass()}
154         *
155         * @param typedGenerator to be set
156         * @return the builder for {@link PropertyMetadataImpl}
157         */
158        public PropertyMetadataBuilder generator(final TypedGenerator<?> typedGenerator) {
159            tempGenerator = typedGenerator;
160            tempPropertyClass = typedGenerator.getType();
161            return this;
162        }
163
164        /**
165         * @see PropertyMetadata#getName()
166         * @param name to be set. must not be null nor empty
167         * @return the builder for {@link PropertyMetadataImpl}
168         */
169        public PropertyMetadataBuilder name(final String name) {
170            tempName = emptyToNull(name);
171            return this;
172        }
173
174        /**
175         * @param defaultValue to be set
176         * @return the builder for {@link PropertyMetadataImpl}
177         */
178        public PropertyMetadataBuilder defaultValue(final boolean defaultValue) {
179            tempDefaultValue = defaultValue;
180            return this;
181        }
182
183        /**
184         * @see PropertyMetadata#getCollectionType()
185         * @param collectionType
186         * @return the builder for {@link PropertyMetadataImpl}
187         */
188        public PropertyMetadataBuilder collectionType(final CollectionType collectionType) {
189            tempCollectionType = collectionType;
190            return this;
191        }
192
193        /**
194         * @param propertyClass to be set
195         * @return the builder for {@link PropertyMetadataImpl}
196         */
197        public PropertyMetadataBuilder propertyClass(final Class<?> propertyClass) {
198            tempPropertyClass = propertyClass;
199            return this;
200        }
201
202        /**
203         * @param required to be set
204         * @return the builder for {@link PropertyMetadataImpl}
205         */
206        public PropertyMetadataBuilder required(final boolean required) {
207            tempRequired = required;
208            return this;
209        }
210
211        /**
212         * @see PropertyMetadata#getPropertyMemberInfo()
213         * @param propertyMemberInfo to be set. must not be null
214         * @return the builder for {@link PropertyMetadataImpl}
215         */
216        public PropertyMetadataBuilder propertyMemberInfo(final PropertyMemberInfo propertyMemberInfo) {
217            requireNonNull(propertyMemberInfo, "objectMemberInfo");
218            tempPropertyMemberInfo = propertyMemberInfo;
219            return this;
220        }
221
222        /**
223         * @return the built {@link PropertyMetadataImpl}
224         */
225        public PropertyMetadataImpl build() {
226            requireNonNull(tempName, "name");
227            requireNonNull(tempGenerator, "generator");
228            requireNonNull(tempPropertyClass, "propertyClass");
229
230            Class<?> actualClass = tempPropertyClass;
231            if (!CollectionType.NO_ITERABLE.equals(tempCollectionType)) {
232                if (CollectionType.ARRAY_MARKER.equals(tempCollectionType)) {
233                    actualClass = Array.newInstance(tempPropertyClass, 0).getClass();
234                } else {
235                    actualClass = tempCollectionType.getIterableType();
236                }
237
238            }
239
240            return new PropertyMetadataImpl(tempName, tempGenerator, tempDefaultValue, tempPropertyClass, actualClass,
241                    tempRequired, tempPropertyAccessStrategy, tempCollectionType, tempPropertyMemberInfo,
242                    tempPropertyReadWrite, tempAssertionStrategy);
243        }
244
245    }
246
247    /**
248     * @return a new instance of {@link PropertyMetadataBuilder}
249     */
250    public static PropertyMetadataBuilder builder() {
251        return new PropertyMetadataBuilder();
252    }
253
254    /**
255     * @param copyFrom to be use as template, must not be null
256     * @return a new instance of {@link PropertyMetadataBuilder} with the content of
257     *         copyFrom being already copied into
258     */
259    public static PropertyMetadataBuilder builder(final PropertyMetadata copyFrom) {
260        final var builder = builder();
261        requireNonNull(copyFrom);
262        builder.collectionType(copyFrom.getCollectionType()).defaultValue(copyFrom.isDefaultValue());
263        builder.generator(copyFrom.getGenerator()).name(copyFrom.getName());
264        builder.propertyAccessStrategy(copyFrom.getPropertyAccessStrategy());
265        builder.propertyClass(copyFrom.getPropertyClass());
266        builder.propertyMemberInfo(copyFrom.getPropertyMemberInfo());
267        builder.propertyReadWrite(copyFrom.getPropertyReadWrite());
268        builder.required(copyFrom.isRequired());
269        builder.assertionStrategy(copyFrom.getAssertionStrategy());
270        return builder;
271    }
272
273    @Override
274    public int compareTo(final PropertyMetadata other) {
275        return name.compareTo(other.getName());
276    }
277
278    @Override
279    public String toString() {
280        final List<Object> elements = new ArrayList<>();
281
282        elements.add("'" + getName() + "' (" + getPropertyClass() + ")");
283
284        if (isRequired()) {
285            elements.add("required");
286        }
287        if (isDefaultValue()) {
288            elements.add("defaultValued");
289        }
290        elements.add(getGenerator());
291        if (!CollectionType.NO_ITERABLE.equals(getCollectionType())) {
292            elements.add(getCollectionType());
293            elements.add("actualClass: " + actualClass);
294        }
295        elements.add(getPropertyReadWrite());
296        elements.add(getPropertyAccessStrategy());
297        if (!AssertionStrategy.DEFAULT.equals(getAssertionStrategy())) {
298            elements.add(getAssertionStrategy());
299        }
300
301        return Joiner.on(", ").join(elements);
302    }
303
304}