001/* 002 * Copyright © 2018. Sir Wellington. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * 006 * You may obtain a copy of the License at 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. 014 */ 015 016package tech.sirwellington.alchemy.test.junit.runners; 017 018import java.lang.annotation.Retention; 019import java.lang.annotation.Target; 020import java.nio.ByteBuffer; 021import java.time.Instant; 022import java.util.Date; 023import java.util.List; 024 025import tech.sirwellington.alchemy.annotations.access.Internal; 026import tech.sirwellington.alchemy.annotations.access.NonInstantiable; 027import tech.sirwellington.alchemy.generator.*; 028 029import static java.lang.annotation.ElementType.FIELD; 030import static java.lang.annotation.RetentionPolicy.RUNTIME; 031import static tech.sirwellington.alchemy.generator.CollectionGenerators.listOf; 032import static tech.sirwellington.alchemy.generator.NumberGenerators.positiveIntegers; 033import static tech.sirwellington.alchemy.generator.NumberGenerators.positiveLongs; 034import static tech.sirwellington.alchemy.generator.ObjectGenerators.pojos; 035import static tech.sirwellington.alchemy.generator.StringGenerators.alphanumericStrings; 036import static tech.sirwellington.alchemy.test.Checks.Internal.checkNotNull; 037import static tech.sirwellington.alchemy.test.Checks.Internal.checkThat; 038 039/** 040 * Used in with the {@link AlchemyTestRunner}, this Annotations allows the Runtime Injection of {@link List} values, using 041 * {@link CollectionGenerators} from the {@link AlchemyGenerator} library. 042 * <p> 043 * Example: 044 * <pre> 045 * {@code 046 * `@RunWith(AlchemyTestRunner.class) 047 * public class ExampleTest 048 * { 049 * 050 * `@GenerateList(String.class) 051 * private List<String> ids; 052 * 053 * ... 054 * } 055 * } 056 * </pre> Note, ticks (`) used to escape Javadocs. 057 * 058 * @author SirWellington 059 * @see GenerateEnum 060 * @see GeneratePojo 061 */ 062@Target(FIELD) 063@Retention(RUNTIME) 064public @interface GenerateList 065{ 066 067 /** 068 * Specify the Generic Type of the List. This is necessary since the type information is erased at Runtime. 069 * 070 * @return 071 */ 072 Class<?> value(); 073 074 /** 075 * The number of elements to include in the list. Defaults to 10. This number must be {@code > 0}. 076 * 077 * @return 078 */ 079 int size() default 10; 080 081 /** 082 * Provide a custom {@linkplain AlchemyGenerator Generator} to use to generate each item. 083 * 084 * @return 085 */ 086 Class<? extends AlchemyGenerator<?>> customGenerator() default Values.NoOpGenerator.class; 087 088 089 @Internal 090 @NonInstantiable 091 class Values 092 { 093 @Internal 094 private class NoOpGenerator implements AlchemyGenerator<String> 095 { 096 @Override 097 public String get() 098 { 099 return ""; 100 } 101 } 102 103 private Values() throws IllegalAccessException 104 { 105 throw new IllegalAccessException("cannot instantiate"); 106 } 107 108 static AlchemyGenerator<List<?>> createGeneratorFor(GenerateList annotation) throws IllegalArgumentException 109 { 110 checkNotNull(annotation, "missing annotation"); 111 final int size = annotation.size(); 112 checkThat(size > 0, "size must be > 0"); 113 114 final AlchemyGenerator<?> generator = determineGeneratorFor(annotation); 115 116 return new AlchemyGenerator<List<?>>() 117 { 118 @Override 119 public List<?> get() 120 { 121 return listOf(generator, size); 122 } 123 }; 124 } 125 126 private static AlchemyGenerator<?> determineGeneratorFor(GenerateList annotation) 127 { 128 Class<? extends AlchemyGenerator<?>> customGeneratorClass = annotation.customGenerator(); 129 130 if (customGeneratorClass != null && customGeneratorClass != NoOpGenerator.class) 131 { 132 checkThat(canInstantiate(customGeneratorClass), "cannot instantiate custom generator: " + customGeneratorClass.getName()); 133 return instantiateGeneratorFrom(customGeneratorClass); 134 } 135 136 Class<?> genericType = annotation.value(); 137 checkNotNull(genericType, "annotation is missing generic type information"); 138 139 if (genericType == String.class) 140 { 141 return alphanumericStrings(); 142 } 143 144 if (genericType == Integer.class) 145 { 146 return positiveIntegers(); 147 } 148 149 if (genericType == Long.class) 150 { 151 return positiveLongs(); 152 } 153 154 if (genericType == Double.class) 155 { 156 return NumberGenerators.doubles(0, 10000); 157 } 158 159 if (Date.class.isAssignableFrom(genericType)) 160 { 161 return DateGenerators.anyTime(); 162 } 163 164 if (genericType == Instant.class) 165 { 166 return TimeGenerators.anytime(); 167 } 168 169 if (genericType == Boolean.class) 170 { 171 return BooleanGenerators.booleans(); 172 } 173 174 if (ByteBuffer.class.isAssignableFrom(genericType)) 175 { 176 return BinaryGenerators.byteBuffers(1024); 177 } 178 179 return pojos(genericType); 180 } 181 182 private static boolean canInstantiate(Class<? extends AlchemyGenerator<?>> customGeneratorClass) 183 { 184 try 185 { 186 AlchemyGenerator<?> generator = customGeneratorClass.newInstance(); 187 return generator != null; 188 } 189 catch (Throwable ex) 190 { 191 return false; 192 } 193 } 194 195 private static AlchemyGenerator<?> instantiateGeneratorFrom(Class<? extends AlchemyGenerator<?>> customGeneratorClass) 196 { 197 try 198 { 199 return customGeneratorClass.newInstance(); 200 } 201 catch (Throwable ex) 202 { 203 throw new IllegalArgumentException("Cannot instantiate Alchemy Generator: " + customGeneratorClass.getName()); 204 } 205 } 206 } 207 208}