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.objects;
017
018import static de.cuioss.tools.collect.CollectionLiterals.immutableList;
019import static java.util.Objects.requireNonNull;
020
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.SortedSet;
029
030import de.cuioss.test.valueobjects.property.PropertyMetadata;
031import de.cuioss.test.valueobjects.property.PropertySupport;
032import de.cuioss.tools.collect.MapBuilder;
033import de.cuioss.tools.string.Joiner;
034import lombok.EqualsAndHashCode;
035import lombok.Getter;
036
037/**
038 * Aggregates all information necessary to dynamically create Objects. In
039 * addition it makes some sanity checks. It provides some convenience methods
040 * for accessing certain views on the properties.
041 *
042 * @author Oliver Wolff
043 */
044@EqualsAndHashCode(of = "allProperties")
045public class RuntimeProperties {
046
047    /**
048     * All {@link PropertyMetadata} contained by this {@link RuntimeProperties}. May
049     * be empty
050     */
051    @Getter
052    private final List<PropertyMetadata> allProperties;
053
054    /**
055     * All {@link PropertyMetadata} contained by this {@link RuntimeProperties} that
056     * are required: {@link PropertyMetadata#isRequired()}. May be an empty list.
057     */
058    @Getter
059    private final List<PropertyMetadata> requiredProperties;
060
061    /**
062     * All {@link PropertyMetadata} contained by this {@link RuntimeProperties} that
063     * are <em>NOT</em> {@link PropertyMetadata#isRequired()}. May be an empty list.
064     */
065    @Getter
066    private final List<PropertyMetadata> additionalProperties;
067
068    /**
069     * All {@link PropertyMetadata} contained by this {@link RuntimeProperties} that
070     * provide a {@link PropertyMetadata#isDefaultValue()}. May be an empty list.
071     */
072    @Getter
073    private final List<PropertyMetadata> defaultProperties;
074
075    /**
076     * All {@link PropertyMetadata} contained by this {@link RuntimeProperties}
077     * where the properties can be written. May be an empty list.
078     */
079    @Getter
080    private final List<PropertyMetadata> writableProperties;
081
082    /**
083     * Constructor.
084     *
085     * @param properties may be null
086     */
087    public RuntimeProperties(final List<? extends PropertyMetadata> properties) {
088        if (null == properties) {
089            allProperties = Collections.emptyList();
090        } else {
091            allProperties = immutableList(properties);
092        }
093        requiredProperties = allProperties.stream().filter(PropertyMetadata::isRequired).toList();
094
095        additionalProperties = allProperties.stream().filter(metadata -> !metadata.isRequired()).toList();
096
097        defaultProperties = allProperties.stream().filter(PropertyMetadata::isDefaultValue).toList();
098
099        writableProperties = allProperties.stream().filter(metadata -> metadata.getPropertyReadWrite().isWriteable())
100                .toList();
101    }
102
103    /**
104     * @param properties
105     */
106    public RuntimeProperties(final SortedSet<PropertyMetadata> properties) {
107        this(immutableList(properties));
108    }
109
110    /**
111     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
112     * of the given {@link Collection}
113     *
114     * @param propertyMetadata  if null or empty an empty list will be returned
115     * @param generateTestValue boolean indicating whether to call
116     *                          {@link PropertySupport#generateTestValue()} on each
117     *                          created element
118     * @return the newly created mutable {@link List}
119     */
120    public static List<PropertySupport> mapToPropertySupport(final Collection<PropertyMetadata> propertyMetadata,
121            final boolean generateTestValue) {
122        final List<PropertySupport> list = new ArrayList<>();
123        if (null == propertyMetadata || propertyMetadata.isEmpty()) {
124            return list;
125        }
126        propertyMetadata.forEach(p -> list.add(new PropertySupport(p)));
127        if (generateTestValue) {
128            list.forEach(PropertySupport::generateTestValue);
129        }
130        return list;
131    }
132
133    /**
134     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
135     * out of {@link #getAllProperties()}
136     *
137     * @param generateTestValue boolean indicating whether to call
138     *                          {@link PropertySupport#generateTestValue()} on each
139     *                          created element
140     * @return the newly created mutable {@link List}
141     */
142    public List<PropertySupport> getAllAsPropertySupport(final boolean generateTestValue) {
143        return mapToPropertySupport(getAllProperties(), generateTestValue);
144    }
145
146    /**
147     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
148     * out of {@link #getAllProperties()} but filtered according to the given names.
149     *
150     * @param generateTestValue boolean indicating whether to call
151     *                          {@link PropertySupport#generateTestValue()} on each
152     *                          created element
153     * @param filter            containing the names to be filtered, must not be
154     *                          null
155     * @return the newly created mutable {@link List}
156     */
157    public List<PropertySupport> getAllAsPropertySupport(final boolean generateTestValue,
158            final Collection<String> filter) {
159        requireNonNull(filter);
160        return getAllAsPropertySupport(generateTestValue).stream().filter(s -> filter.contains(s.getName())).toList();
161    }
162
163    /**
164     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
165     * out of {@link #getRequiredProperties()}
166     *
167     * @param generateTestValue boolean indicating whether to call
168     *                          {@link PropertySupport#generateTestValue()} on each
169     *                          created element
170     * @return the newly created mutable {@link List}
171     */
172    public List<PropertySupport> getRequiredAsPropertySupport(final boolean generateTestValue) {
173        return mapToPropertySupport(getRequiredProperties(), generateTestValue);
174    }
175
176    /**
177     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
178     * out of {@link #getRequiredProperties()} but filtered according to the given
179     * names.
180     *
181     * @param generateTestValue boolean indicating whether to call
182     *                          {@link PropertySupport#generateTestValue()} on each
183     *                          created element
184     * @param filter            containing the names to be filtered, must not be
185     *                          null
186     * @return the newly created mutable {@link List}
187     */
188    public List<PropertySupport> getRequiredAsPropertySupport(final boolean generateTestValue,
189            final Collection<String> filter) {
190        requireNonNull(filter);
191        return getRequiredAsPropertySupport(generateTestValue).stream().filter(s -> filter.contains(s.getName()))
192                .toList();
193    }
194
195    /**
196     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
197     * out of {@link #getDefaultProperties()}
198     *
199     * @param generateTestValue boolean indicating whether to call
200     *                          {@link PropertySupport#generateTestValue()} on each
201     *                          created element
202     * @return the newly created mutable {@link List}
203     */
204    public List<PropertySupport> getDefaultAsPropertySupport(final boolean generateTestValue) {
205        return mapToPropertySupport(getDefaultProperties(), generateTestValue);
206    }
207
208    /**
209     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
210     * out of {@link #getDefaultProperties()} but filtered according to the given
211     * names.
212     *
213     * @param generateTestValue boolean indicating whether to call
214     *                          {@link PropertySupport#generateTestValue()} on each
215     *                          created element
216     * @param filter            containing the names to be filtered, must not be
217     *                          null
218     * @return the newly created mutable {@link List}
219     */
220    public List<PropertySupport> getDefaultAsPropertySupport(final boolean generateTestValue,
221            final Collection<String> filter) {
222        requireNonNull(filter);
223        return getDefaultAsPropertySupport(generateTestValue).stream().filter(s -> filter.contains(s.getName()))
224                .toList();
225    }
226
227    /**
228     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
229     * out of {@link #getAdditionalProperties()}
230     *
231     * @param generateTestValue boolean indicating whether to call
232     *                          {@link PropertySupport#generateTestValue()} on each
233     *                          created element
234     * @return the newly created mutable {@link List}
235     */
236    public List<PropertySupport> getAdditionalAsPropertySupport(final boolean generateTestValue) {
237        return mapToPropertySupport(getAdditionalProperties(), generateTestValue);
238    }
239
240    /**
241     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
242     * out of {@link #getAdditionalProperties()}
243     *
244     * @param generateTestValue boolean indicating whether to call
245     *                          {@link PropertySupport#generateTestValue()} on each
246     *                          created element
247     * @param filter            containing the names to be filtered, must not be
248     *                          null
249     * @return the newly created mutable {@link List}
250     */
251    public List<PropertySupport> getAdditionalAsPropertySupport(final boolean generateTestValue,
252            final Collection<String> filter) {
253        requireNonNull(filter);
254        return getAdditionalAsPropertySupport(generateTestValue).stream().filter(s -> filter.contains(s.getName()))
255                .toList();
256    }
257
258    /**
259     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
260     * out of {@link #getWritableProperties()}
261     *
262     * @param generateTestValue boolean indicating whether to call
263     *                          {@link PropertySupport#generateTestValue()} on each
264     *                          created element
265     * @return the newly created mutable {@link List}
266     */
267    public List<PropertySupport> getWritableAsPropertySupport(final boolean generateTestValue) {
268        return mapToPropertySupport(getWritableProperties(), generateTestValue);
269    }
270
271    /**
272     * Creates a list of {@link PropertySupport} for each {@link PropertyMetadata}
273     * out of {@link #getWritableProperties()}
274     *
275     * @param generateTestValue boolean indicating whether to call
276     *                          {@link PropertySupport#generateTestValue()} on each
277     *                          created element
278     * @param filter            containing the names to be filtered, must not be
279     *                          null
280     * @return the newly created mutable {@link List}
281     */
282    public List<PropertySupport> getWritableAsPropertySupport(final boolean generateTestValue,
283            final Collection<String> filter) {
284        requireNonNull(filter);
285        return getWritableAsPropertySupport(generateTestValue).stream().filter(s -> filter.contains(s.getName()))
286                .toList();
287    }
288
289    /**
290     * @param generateTestValue boolean indicating whether to call
291     *                          {@link PropertySupport#generateTestValue()} on each
292     *                          created element
293     * @return a map view on all {@link PropertyMetadata} as {@link PropertySupport}
294     *         with the property names as key
295     */
296    public Map<String, PropertySupport> asMapView(final boolean generateTestValue) {
297        var builder = new MapBuilder<String, PropertySupport>();
298        getAllAsPropertySupport(generateTestValue).forEach(p -> builder.put(p.getName(), p));
299        return builder.toImmutableMap();
300    }
301
302    /**
303     * Extracts the names of a given {@link Collection} of {@link PropertyMetadata}
304     *
305     * @param metadata if it is null or empty an empty {@link Set} will be returned
306     * @return a set of the extracted names.
307     */
308    public static final Set<String> extractNames(final Collection<PropertyMetadata> metadata) {
309        if (null == metadata || metadata.isEmpty()) {
310            return Collections.emptySet();
311        }
312        final Set<String> builder = new HashSet<>();
313        metadata.forEach(m -> builder.add(m.getName()));
314        return builder;
315    }
316
317    @Override
318    public String toString() {
319        final var builder = new StringBuilder(getClass().getName());
320        builder.append("\nRequired properties: ").append(getPropertyNames(requiredProperties));
321        builder.append("\nAdditional properties: ").append(getPropertyNames(additionalProperties));
322        builder.append("\nDefault valued properties: ").append(getPropertyNames(defaultProperties));
323        builder.append("\nWritable properties: ").append(getPropertyNames(writableProperties));
324        return builder.toString();
325    }
326
327    private static String getPropertyNames(final List<PropertyMetadata> properties) {
328        if (null == properties || properties.isEmpty()) {
329            return "-";
330        }
331        final List<String> names = new ArrayList<>();
332        properties.forEach(p -> names.add(p.getName()));
333        return Joiner.on(", ").join(names);
334    }
335}