/*
 * Copyright 2002-2015 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
 *
 *      https://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.context.event;

import org.junit.Test;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ResolvableType;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * @author Stephane Nicoll
 */
public class GenericApplicationListenerAdapterTests extends AbstractApplicationEventListenerTests {

	@Test
	public void supportsEventTypeWithSmartApplicationListener() {
		SmartApplicationListener smartListener = mock(SmartApplicationListener.class);
		GenericApplicationListenerAdapter listener = new GenericApplicationListenerAdapter(smartListener);
		ResolvableType type = ResolvableType.forClass(ApplicationEvent.class);
		listener.supportsEventType(type);
		verify(smartListener, times(1)).supportsEventType(ApplicationEvent.class);
	}

	@Test
	public void supportsSourceTypeWithSmartApplicationListener() {
		SmartApplicationListener smartListener = mock(SmartApplicationListener.class);
		GenericApplicationListenerAdapter listener = new GenericApplicationListenerAdapter(smartListener);
		listener.supportsSourceType(Object.class);
		verify(smartListener, times(1)).supportsSourceType(Object.class);
	}

	@Test
	public void genericListenerStrictType() {
		supportsEventType(true, StringEventListener.class, getGenericApplicationEventType("stringEvent"));
	}

	@Test // Demonstrates we can't inject that event because the generic type is lost
	public void genericListenerStrictTypeTypeErasure() {
		GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
		ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
		supportsEventType(false, StringEventListener.class, eventType);
	}

	@Test // But it works if we specify the type properly
	public void genericListenerStrictTypeAndResolvableType() {
		ResolvableType eventType = ResolvableType
				.forClassWithGenerics(GenericTestEvent.class, String.class);
		supportsEventType(true, StringEventListener.class, eventType);
	}

	@Test // or if the event provides its precise type
	public void genericListenerStrictTypeAndResolvableTypeProvider() {
		ResolvableType eventType = new SmartGenericTestEvent<>(this, "foo").getResolvableType();
		supportsEventType(true, StringEventListener.class, eventType);
	}

	@Test // Demonstrates it works if we actually use the subtype
	public void genericListenerStrictTypeEventSubType() {
		StringEvent stringEvent = new StringEvent(this, "test");
		ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
		supportsEventType(true, StringEventListener.class, eventType);
	}

	@Test
	public void genericListenerStrictTypeNotMatching() {
		supportsEventType(false, StringEventListener.class, getGenericApplicationEventType("longEvent"));
	}

	@Test
	public void genericListenerStrictTypeEventSubTypeNotMatching() {
		LongEvent stringEvent = new LongEvent(this, 123L);
		ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
		supportsEventType(false, StringEventListener.class, eventType);
	}

	@Test
	public void genericListenerStrictTypeNotMatchTypeErasure() {
		GenericTestEvent<Long> longEvent = createGenericTestEvent(123L);
		ResolvableType eventType = ResolvableType.forType(longEvent.getClass());
		supportsEventType(false, StringEventListener.class, eventType);
	}

	@Test
	public void genericListenerStrictTypeSubClass() {
		supportsEventType(false, ObjectEventListener.class,
				getGenericApplicationEventType("longEvent"));
	}

	@Test
	public void genericListenerUpperBoundType() {
		supportsEventType(true, UpperBoundEventListener.class,
				getGenericApplicationEventType("illegalStateExceptionEvent"));
	}

	@Test
	public void genericListenerUpperBoundTypeNotMatching() throws NoSuchFieldException {
		supportsEventType(false, UpperBoundEventListener.class,
				getGenericApplicationEventType("ioExceptionEvent"));
	}

	@Test
	public void genericListenerWildcardType() {
		supportsEventType(true, GenericEventListener.class,
				getGenericApplicationEventType("stringEvent"));
	}

	@Test  // Demonstrates we cant inject that event because the listener has a wildcard
	public void genericListenerWildcardTypeTypeErasure() {
		GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
		ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
		supportsEventType(true, GenericEventListener.class, eventType);
	}

	@Test
	public void genericListenerRawType() {
		supportsEventType(true, RawApplicationListener.class,
				getGenericApplicationEventType("stringEvent"));
	}

	@Test  // Demonstrates we cant inject that event because the listener has a raw type
	public void genericListenerRawTypeTypeErasure() {
		GenericTestEvent<String> stringEvent = createGenericTestEvent("test");
		ResolvableType eventType = ResolvableType.forType(stringEvent.getClass());
		supportsEventType(true, RawApplicationListener.class, eventType);
	}

	private void supportsEventType(boolean match, Class<? extends ApplicationListener> listenerType,
			ResolvableType eventType) {

		ApplicationListener<?> listener = mock(listenerType);
		GenericApplicationListenerAdapter adapter = new GenericApplicationListenerAdapter(listener);
		assertEquals("Wrong match for event '" + eventType + "' on " + listenerType.getClass().getName(),
				match, adapter.supportsEventType(eventType));
	}

}
