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.contract; 017 018import static java.util.Objects.requireNonNull; 019 020import java.util.ArrayList; 021import java.util.List; 022 023import de.cuioss.test.valueobjects.api.TestContract; 024import de.cuioss.test.valueobjects.api.contracts.VerifyConstructor; 025import de.cuioss.test.valueobjects.api.contracts.VerifyConstructors; 026import de.cuioss.test.valueobjects.api.contracts.VerifyFactoryMethod; 027import de.cuioss.test.valueobjects.api.contracts.VerifyFactoryMethods; 028import de.cuioss.test.valueobjects.objects.ParameterizedInstantiator; 029import de.cuioss.test.valueobjects.objects.RuntimeProperties; 030import de.cuioss.test.valueobjects.objects.impl.ConstructorBasedInstantiator; 031import de.cuioss.test.valueobjects.objects.impl.FactoryBasedInstantiator; 032import de.cuioss.test.valueobjects.property.PropertyMetadata; 033import de.cuioss.test.valueobjects.property.PropertySupport; 034import de.cuioss.test.valueobjects.util.AnnotationHelper; 035import de.cuioss.tools.collect.CollectionBuilder; 036import de.cuioss.tools.logging.CuiLogger; 037import lombok.Getter; 038import lombok.NonNull; 039import lombok.RequiredArgsConstructor; 040 041/** 042 * TestContract for dealing Constructor and factories, {@link VerifyConstructor} 043 * and {@link VerifyFactoryMethod} respectively 044 * 045 * @author Oliver Wolff 046 * @param <T> identifying the objects to be tested. 047 */ 048@RequiredArgsConstructor 049public class ObjectCreatorContractImpl<T> implements TestContract<T> { 050 051 private static final CuiLogger log = new CuiLogger(ObjectCreatorContractImpl.class); 052 053 @Getter 054 @NonNull 055 private final ParameterizedInstantiator<T> instantiator; 056 057 @Override 058 public void assertContract() { 059 final var builder = new StringBuilder("Verifying "); 060 builder.append(getClass().getName()).append("\nWith configuration: ").append(instantiator.toString()); 061 log.info(builder.toString()); 062 063 shouldPersistAllParameter(); 064 shouldHandleRequiredAndDefaults(); 065 shouldFailOnMissingRequiredAttributes(); 066 } 067 068 private void shouldFailOnMissingRequiredAttributes() { 069 final var information = getInstantiator().getRuntimeProperties(); 070 final var required = information.getRequiredAsPropertySupport(true); 071 072 for (final PropertySupport support : required) { 073 if (!support.isPrimitive()) { 074 final List<PropertySupport> iterating = new ArrayList<>(required); 075 iterating.remove(support); 076 iterating.add(support.createCopy(false)); 077 var failed = false; 078 try { 079 getInstantiator().newInstance(iterating, false); 080 failed = true; 081 } catch (final AssertionError e) { 082 // expected 083 } 084 if (failed) { 085 throw new AssertionError("Object Should not build due to missing required attribute " + support); 086 } 087 } 088 } 089 } 090 091 private void shouldHandleRequiredAndDefaults() { 092 final var information = getInstantiator().getRuntimeProperties(); 093 094 final var required = information.getRequiredAsPropertySupport(true); 095 final var instance = getInstantiator().newInstance(required, false); 096 097 for (final PropertySupport support : required) { 098 if (support.isReadable()) { 099 support.assertValueSet(instance); 100 } 101 } 102 103 for (final PropertySupport support : information.getDefaultAsPropertySupport(false)) { 104 if (support.isReadable()) { 105 support.assertDefaultValue(instance); 106 } 107 } 108 109 for (final PropertySupport support : information.getAdditionalAsPropertySupport(false)) { 110 if (support.isReadable() && !support.isDefaultValue()) { 111 support.assertValueSet(instance); 112 } 113 } 114 115 } 116 117 private void shouldPersistAllParameter() { 118 final var properties = instantiator.getRuntimeProperties().getAllAsPropertySupport(true); 119 120 final var instance = instantiator.newInstance(properties, false); 121 for (final PropertySupport support : properties) { 122 if (support.isReadable()) { 123 support.assertValueSet(instance); 124 } 125 } 126 } 127 128 /** 129 * Factory method for creating a {@link List} of instances of 130 * {@link ObjectCreatorContractImpl} depending on the given parameter 131 * 132 * @param beanType identifying the type to be tested. Must not be 133 * null 134 * @param annotated the annotated unit-test-class. It is expected 135 * to be annotated with {@link VerifyConstructor} 136 * and / or {@link VerifyConstructors}, 137 * {@link VerifyFactoryMethod} and / or 138 * {@link VerifyFactoryMethods} otherwise the 139 * method will return empty list 140 * @param initialPropertyMetadata identifying the complete set of 141 * {@link PropertyMetadata}, where the actual 142 * {@link PropertyMetadata} for the test will be 143 * filtered by using the attributes defined 144 * within {@link VerifyConstructor} and / or 145 * {@link VerifyFactoryMethod}. Must not be null. 146 * @return a {@link List} of instances of {@link ObjectCreatorContractImpl} in 147 * case all requirements for the parameters are correct, otherwise it 148 * will return an empty list 149 */ 150 public static final <T> List<ObjectCreatorContractImpl<T>> createTestContracts(final Class<T> beanType, 151 final Class<?> annotated, final List<PropertyMetadata> initialPropertyMetadata) { 152 153 requireNonNull(beanType, "beantype must not be null"); 154 requireNonNull(initialPropertyMetadata, "initialPropertyMetadata must not be null"); 155 156 final var builder = new CollectionBuilder<ObjectCreatorContractImpl<T>>(); 157 // VerifyConstructor 158 for (final VerifyConstructor contract : AnnotationHelper.extractConfiguredConstructorContracts(annotated)) { 159 final var properties = AnnotationHelper.constructorConfigToPropertyMetadata(contract, 160 initialPropertyMetadata); 161 final ParameterizedInstantiator<T> instantiator = new ConstructorBasedInstantiator<>(beanType, 162 new RuntimeProperties(properties)); 163 builder.add(new ObjectCreatorContractImpl<>(instantiator)); 164 } 165 // Verify Factory method 166 for (final VerifyFactoryMethod contract : AnnotationHelper.extractConfiguredFactoryContracts(annotated)) { 167 final var properties = AnnotationHelper.factoryConfigToPropertyMetadata(contract, initialPropertyMetadata); 168 Class<?> enclosingType = beanType; 169 if (!VerifyFactoryMethod.class.equals(contract.enclosingType())) { 170 enclosingType = contract.enclosingType(); 171 } 172 final ParameterizedInstantiator<T> instantiator = new FactoryBasedInstantiator<>(beanType, 173 new RuntimeProperties(properties), enclosingType, contract.factoryMethodName()); 174 builder.add(new ObjectCreatorContractImpl<>(instantiator)); 175 } 176 return builder.toImmutableList(); 177 } 178 179}