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}