/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.test.mock.mockito; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; import org.springframework.util.StringUtils; /** * Parser to create {@link MockDefinition} and {@link SpyDefinition} instances from * {@link MockBean @MockBean} and {@link SpyBean @SpyBean} annotations declared on or in a * class. * * @author Phillip Webb * @author Stephane Nicoll */ class DefinitionsParser { private final Set<Definition> definitions; private final Map<Definition, Field> definitionFields; DefinitionsParser() { this(Collections.<Definition>emptySet()); } DefinitionsParser(Collection<? extends Definition> existing) { this.definitions = new LinkedHashSet<>(); this.definitionFields = new LinkedHashMap<>(); if (existing != null) { this.definitions.addAll(existing); } } public void parse(Class<?> source) { parseElement(source); ReflectionUtils.doWithFields(source, new FieldCallback() { @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { parseElement(field); } }); } private void parseElement(AnnotatedElement element) { for (MockBean annotation : AnnotationUtils.getRepeatableAnnotations(element, MockBean.class, MockBeans.class)) { parseMockBeanAnnotation(annotation, element); } for (SpyBean annotation : AnnotationUtils.getRepeatableAnnotations(element, SpyBean.class, SpyBeans.class)) { parseSpyBeanAnnotation(annotation, element); } } private void parseMockBeanAnnotation(MockBean annotation, AnnotatedElement element) { Set<ResolvableType> typesToMock = getOrDeduceTypes(element, annotation.value()); Assert.state(!typesToMock.isEmpty(), "Unable to deduce type to mock from " + element); if (StringUtils.hasLength(annotation.name())) { Assert.state(typesToMock.size() == 1, "The name attribute can only be used when mocking a single class"); } for (ResolvableType typeToMock : typesToMock) { MockDefinition definition = new MockDefinition(annotation.name(), typeToMock, annotation.extraInterfaces(), annotation.answer(), annotation.serializable(), annotation.reset(), QualifierDefinition.forElement(element)); addDefinition(element, definition, "mock"); } } private void parseSpyBeanAnnotation(SpyBean annotation, AnnotatedElement element) { Set<ResolvableType> typesToSpy = getOrDeduceTypes(element, annotation.value()); Assert.state(!typesToSpy.isEmpty(), "Unable to deduce type to spy from " + element); if (StringUtils.hasLength(annotation.name())) { Assert.state(typesToSpy.size() == 1, "The name attribute can only be used when spying a single class"); } for (ResolvableType typeToSpy : typesToSpy) { SpyDefinition definition = new SpyDefinition(annotation.name(), typeToSpy, annotation.reset(), annotation.proxyTargetAware(), QualifierDefinition.forElement(element)); addDefinition(element, definition, "spy"); } } private void addDefinition(AnnotatedElement element, Definition definition, String type) { boolean isNewDefinition = this.definitions.add(definition); Assert.state(isNewDefinition, "Duplicate " + type + " definition " + definition); if (element instanceof Field) { Field field = (Field) element; this.definitionFields.put(definition, field); } } private Set<ResolvableType> getOrDeduceTypes(AnnotatedElement element, Class<?>[] value) { Set<ResolvableType> types = new LinkedHashSet<>(); for (Class<?> clazz : value) { types.add(ResolvableType.forClass(clazz)); } if (types.isEmpty() && element instanceof Field) { types.add(ResolvableType.forField((Field) element)); } return types; } public Set<Definition> getDefinitions() { return Collections.unmodifiableSet(this.definitions); } public Field getField(Definition definition) { return this.definitionFields.get(definition); } }