/*
 * Copyright 2002-2008 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.core;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;

import junit.framework.TestCase;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Rob Harrop
 * @author Juergen Hoeller
 */
public class BridgeMethodResolverTests extends TestCase {

	private static TypeVariable findTypeVariable(Class clazz, String name) {
		TypeVariable[] variables = clazz.getTypeParameters();
		for (int i = 0; i < variables.length; i++) {
			TypeVariable variable = variables[i];
			if (variable.getName().equals(name)) {
				return variable;
			}
		}
		return null;
	}

	private static Method findMethodWithReturnType(String name, Class returnType, Class targetType) {
		Method[] methods = targetType.getMethods();
		for (Method m : methods) {
			if (m.getName().equals(name) && m.getReturnType().equals(returnType)) {
				return m;
			}
		}
		return null;
	}


	public void testFindBridgedMethod() throws Exception {
		Method unbridged = MyFoo.class.getDeclaredMethod("someMethod", String.class, Object.class);
		Method bridged = MyFoo.class.getDeclaredMethod("someMethod", Serializable.class, Object.class);
		assertFalse(unbridged.isBridge());
		assertTrue(bridged.isBridge());

		assertEquals("Unbridged method not returned directly", unbridged, BridgeMethodResolver.findBridgedMethod(unbridged));
		assertEquals("Incorrect bridged method returned", unbridged, BridgeMethodResolver.findBridgedMethod(bridged));
	}

	public void testFindBridgedVarargMethod() throws Exception {
		Method unbridged = MyFoo.class.getDeclaredMethod("someVarargMethod", String.class, Object[].class);
		Method bridged = MyFoo.class.getDeclaredMethod("someVarargMethod", Serializable.class, Object[].class);
		assertFalse(unbridged.isBridge());
		assertTrue(bridged.isBridge());

		assertEquals("Unbridged method not returned directly", unbridged, BridgeMethodResolver.findBridgedMethod(unbridged));
		assertEquals("Incorrect bridged method returned", unbridged, BridgeMethodResolver.findBridgedMethod(bridged));
	}

	public void testFindBridgedMethodInHierarchy() throws Exception {
		Method unbridged = DateAdder.class.getMethod("add", Date.class);
		Method bridged = DateAdder.class.getMethod("add", Object.class);
		assertFalse(unbridged.isBridge());
		assertTrue(bridged.isBridge());

		assertEquals("Unbridged method not returned directly", unbridged, BridgeMethodResolver.findBridgedMethod(unbridged));
		assertEquals("Incorrect bridged method returned", unbridged, BridgeMethodResolver.findBridgedMethod(bridged));
	}

	public void testIsBridgeMethodFor() throws Exception {
		Map typeParameterMap = GenericTypeResolver.getTypeVariableMap(MyBar.class);
		Method bridged = MyBar.class.getDeclaredMethod("someMethod", String.class, Object.class);
		Method other = MyBar.class.getDeclaredMethod("someMethod", Integer.class, Object.class);
		Method bridge = MyBar.class.getDeclaredMethod("someMethod", Object.class, Object.class);

		assertTrue("Should be bridge method", BridgeMethodResolver.isBridgeMethodFor(bridge, bridged, typeParameterMap));
		assertFalse("Should not be bridge method", BridgeMethodResolver.isBridgeMethodFor(bridge, other, typeParameterMap));
	}

	public void testCreateTypeVariableMap() throws Exception {
		Map<String, Class> typeVariableMap = GenericTypeResolver.getTypeVariableMap(MyBar.class);
		TypeVariable barT = findTypeVariable(InterBar.class, "T");
		assertEquals(String.class, typeVariableMap.get(barT));

		typeVariableMap = GenericTypeResolver.getTypeVariableMap(MyFoo.class);
		TypeVariable fooT = findTypeVariable(Foo.class, "T");
		assertEquals(String.class, typeVariableMap.get(fooT));

		typeVariableMap = GenericTypeResolver.getTypeVariableMap(ExtendsEnclosing.ExtendsEnclosed.ExtendsReallyDeepNow.class);
		TypeVariable r = findTypeVariable(Enclosing.Enclosed.ReallyDeepNow.class, "R");
		TypeVariable s = findTypeVariable(Enclosing.Enclosed.class, "S");
		TypeVariable t = findTypeVariable(Enclosing.class, "T");
		assertEquals(Long.class, typeVariableMap.get(r));
		assertEquals(Integer.class, typeVariableMap.get(s));
		assertEquals(String.class, typeVariableMap.get(t));
	}

	public void testDoubleParameterization() throws Exception {
		Method objectBridge = MyBoo.class.getDeclaredMethod("foo", Object.class);
		Method serializableBridge = MyBoo.class.getDeclaredMethod("foo", Serializable.class);

		Method stringFoo = MyBoo.class.getDeclaredMethod("foo", String.class);
		Method integerFoo = MyBoo.class.getDeclaredMethod("foo", Integer.class);

		assertEquals("foo(String) not resolved.", stringFoo, BridgeMethodResolver.findBridgedMethod(objectBridge));
		assertEquals("foo(Integer) not resolved.", integerFoo, BridgeMethodResolver.findBridgedMethod(serializableBridge));
	}

	public void testFindBridgedMethodFromMultipleBridges() throws Exception {
		Method loadWithObjectReturn = findMethodWithReturnType("load", Object.class, SettingsDaoImpl.class);
		assertNotNull(loadWithObjectReturn);

		Method loadWithSettingsReturn = findMethodWithReturnType("load", Settings.class, SettingsDaoImpl.class);
		assertNotNull(loadWithSettingsReturn);

		assertNotSame(loadWithObjectReturn, loadWithSettingsReturn);

		Method method = SettingsDaoImpl.class.getMethod("load");
		assertNotNull(method);

		assertEquals(method, BridgeMethodResolver.findBridgedMethod(loadWithObjectReturn));
		assertEquals(method, BridgeMethodResolver.findBridgedMethod(loadWithSettingsReturn));
	}

	public void testFindBridgedMethodFromParent() throws Exception {
		Method loadFromParentBridge = SettingsDaoImpl.class.getMethod("loadFromParent");
		assertNotNull(loadFromParentBridge);
		assertTrue(loadFromParentBridge.isBridge());

		Method loadFromParent = AbstractDaoImpl.class.getMethod("loadFromParent");
		assertNotNull(loadFromParent);
		assertFalse(loadFromParent.isBridge());

		assertEquals(loadFromParent, BridgeMethodResolver.findBridgedMethod(loadFromParentBridge));
	}

	public void testWithSingleBoundParameterizedOnInstantiate() throws Exception {
		Method bridgeMethod = DelayQueue.class.getMethod("add", Object.class);
		assertTrue(bridgeMethod.isBridge());
		Method actualMethod = DelayQueue.class.getMethod("add", Delayed.class);
		assertFalse(actualMethod.isBridge());
		assertEquals(actualMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testWithDoubleBoundParameterizedOnInstantiate() throws Exception {
		Method bridgeMethod = SerializableBounded.class.getMethod("boundedOperation", Object.class);
		assertTrue(bridgeMethod.isBridge());
		Method actualMethod = SerializableBounded.class.getMethod("boundedOperation", HashMap.class);
		assertFalse(actualMethod.isBridge());
		assertEquals(actualMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testWithGenericParameter() throws Exception {
		Method[] methods = StringGenericParameter.class.getMethods();
		Method bridgeMethod = null;
		Method bridgedMethod = null;
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) {
				if (method.getReturnType().equals(Object.class)) {
					bridgeMethod = method;
				}
				else {
					bridgedMethod = method;
				}
			}
		}
		assertNotNull("bridgedMethod should not be null", bridgedMethod);
		assertNotNull("bridgeMethod should not be null", bridgeMethod);
		assertTrue(bridgeMethod.isBridge());
		assertFalse(bridgedMethod.isBridge());
		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testOnAllMethods() throws Exception {
		Method[] methods = StringList.class.getMethods();
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			assertNotNull(BridgeMethodResolver.findBridgedMethod(method));
		}
	}

	public void testSPR2583() throws Exception {
		Method bridgedMethod = MessageBroadcasterImpl.class.getMethod("receive", MessageEvent.class);
		assertFalse(bridgedMethod.isBridge());
		Method bridgeMethod = MessageBroadcasterImpl.class.getMethod("receive", Event.class);
		assertTrue(bridgeMethod.isBridge());

		Method otherMethod = MessageBroadcasterImpl.class.getMethod("receive", NewMessageEvent.class);
		assertFalse(otherMethod.isBridge());

		Map typeVariableMap = GenericTypeResolver.getTypeVariableMap(MessageBroadcasterImpl.class);
		assertFalse("Match identified incorrectly", BridgeMethodResolver.isBridgeMethodFor(bridgeMethod, otherMethod, typeVariableMap));
		assertTrue("Match not found correctly", BridgeMethodResolver.isBridgeMethodFor(bridgeMethod, bridgedMethod, typeVariableMap));

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR2454() throws Exception {
		Map typeVariableMap = GenericTypeResolver.getTypeVariableMap(YourHomer.class);
		TypeVariable variable = findTypeVariable(MyHomer.class, "L");
		assertEquals(AbstractBounded.class, ((ParameterizedType) typeVariableMap.get(variable)).getRawType());
	}

	public void testSPR2603() throws Exception {
		Method objectBridge = YourHomer.class.getDeclaredMethod("foo", Bounded.class);
		Method abstractBoundedFoo = YourHomer.class.getDeclaredMethod("foo", AbstractBounded.class);

		Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(objectBridge);
		assertEquals("foo(AbstractBounded) not resolved.", abstractBoundedFoo, bridgedMethod);
	}

	public void testSPR2648() throws Exception {
		Method bridgeMethod = GenericSqlMapIntegerDao.class.getDeclaredMethod("saveOrUpdate", Object.class);
		assertTrue(bridgeMethod.isBridge());

		Method bridgedMethod = GenericSqlMapIntegerDao.class.getDeclaredMethod("saveOrUpdate", Integer.class);
		assertFalse(bridgedMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR2763() throws Exception {
		Method bridgedMethod = AbstractDao.class.getDeclaredMethod("save", Object.class);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = UserDaoImpl.class.getDeclaredMethod("save", User.class);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3041() throws Exception {
		Method bridgedMethod = BusinessDao.class.getDeclaredMethod("save", Business.class);
		assertNotNull(bridgedMethod);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = BusinessDao.class.getDeclaredMethod("save", Object.class);
		assertNotNull(bridgeMethod);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3173() throws Exception {
		Method bridgedMethod = UserDaoImpl.class.getDeclaredMethod("saveVararg", User.class, Object[].class);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = UserDaoImpl.class.getDeclaredMethod("saveVararg", Object.class, Object[].class);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3304() throws Exception {
		Method bridgedMethod = MegaMessageProducerImpl.class.getDeclaredMethod("receive", MegaMessageEvent.class);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod  = MegaMessageProducerImpl.class.getDeclaredMethod("receive", MegaEvent.class);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3324() throws Exception {
		Method bridgedMethod = BusinessDao.class.getDeclaredMethod("get", Long.class);
		assertNotNull(bridgedMethod);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = BusinessDao.class.getDeclaredMethod("get", Object.class);
		assertNotNull(bridgeMethod);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3357() throws Exception {
		Method bridgedMethod = ExtendsAbstractImplementsInterface.class.getDeclaredMethod(
				"doSomething", DomainObjectExtendsSuper.class, Object.class);
		assertNotNull(bridgedMethod);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = ExtendsAbstractImplementsInterface.class.getDeclaredMethod(
				"doSomething", DomainObjectSuper.class, Object.class);
		assertNotNull(bridgeMethod);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3485() throws Exception {
		Method bridgedMethod = DomainObject.class.getDeclaredMethod(
				"method2", ParameterType.class, byte[].class);
		assertNotNull(bridgedMethod);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = DomainObject.class.getDeclaredMethod(
				"method2", Serializable.class, Object.class);
		assertNotNull(bridgeMethod);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}

	public void testSPR3534() throws Exception {
		Method bridgedMethod = TestEmailProvider.class.getDeclaredMethod(
				"findBy", EmailSearchConditions.class);
		assertNotNull(bridgedMethod);
		assertFalse(bridgedMethod.isBridge());

		Method bridgeMethod = TestEmailProvider.class.getDeclaredMethod(
				"findBy", Object.class);
		assertNotNull(bridgeMethod);
		assertTrue(bridgeMethod.isBridge());

		assertEquals(bridgedMethod, BridgeMethodResolver.findBridgedMethod(bridgeMethod));
	}


	public static interface Foo<T extends Serializable> {

		void someMethod(T theArg, Object otherArg);

		void someVarargMethod(T theArg, Object... otherArg);
	}


	public static class MyFoo implements Foo<String> {

		public void someMethod(Integer theArg, Object otherArg) {
		}

		public void someMethod(String theArg, Object otherArg) {
		}

		public void someVarargMethod(String theArg, Object... otherArgs) {
		}
	}


	public static abstract class Bar<T> {

		void someMethod(Map m, Object otherArg) {
		}

		void someMethod(T theArg, Map m) {
		}

		abstract void someMethod(T theArg, Object otherArg);
	}


	public static abstract class InterBar<T> extends Bar<T> {

	}


	public static class MyBar extends InterBar<String> {

		public void someMethod(String theArg, Object otherArg) {
		}

		public void someMethod(Integer theArg, Object otherArg) {
		}
	}


	public interface Adder<T> {

		void add(T item);
	}


	public abstract class AbstractDateAdder implements Adder<Date> {

		public abstract void add(Date date);
	}


	public class DateAdder extends AbstractDateAdder {

		@Override
		public void add(Date date) {
		}
	}


	public class Enclosing<T> {

		public class Enclosed<S> {

			public class ReallyDeepNow<R> {

				void someMethod(S s, T t, R r) {
				}
			}
		}
	}


	public class ExtendsEnclosing extends Enclosing<String> {

		public class ExtendsEnclosed extends Enclosed<Integer> {

			public class ExtendsReallyDeepNow extends ReallyDeepNow<Long> {

				void someMethod(Integer s, String t, Long r) {
					throw new UnsupportedOperationException();
				}
			}
		}
	}


	public interface Boo<E, T extends Serializable> {

		void foo(E e);

		void foo(T t);
	}


	public class MyBoo implements Boo<String, Integer> {

		public void foo(String e) {
			throw new UnsupportedOperationException();
		}

		public void foo(Integer t) {
			throw new UnsupportedOperationException();
		}
	}


	interface Settings {

	}


	interface ConcreteSettings extends Settings {

	}


	interface Dao<T, S> {

		T load();

		S loadFromParent();
	}


	interface SettingsDao<T extends Settings, S> extends Dao<T, S> {

		T load();
	}


	interface ConcreteSettingsDao extends SettingsDao<ConcreteSettings, String> {

		String loadFromParent();
	}


	abstract class AbstractDaoImpl<T, S> implements Dao<T, S> {

		protected T object;

		protected S otherObject;

		protected AbstractDaoImpl(T object, S otherObject) {
			this.object = object;
			this.otherObject = otherObject;
		}

		@Transactional(readOnly = true)
		public S loadFromParent() {
			return otherObject;
		}
	}


	class SettingsDaoImpl extends AbstractDaoImpl<ConcreteSettings, String> implements ConcreteSettingsDao {

		protected SettingsDaoImpl(ConcreteSettings object) {
			super(object, "From Parent");
		}

		@Transactional(readOnly = true)
		public ConcreteSettings load() {
			return super.object;
		}
	}


	private static interface Bounded<E> {

		boolean boundedOperation(E e);
	}


	private static class AbstractBounded<E> implements Bounded<E> {

		public boolean boundedOperation(E myE) {
			return true;
		}
	}


	private static class SerializableBounded<E extends HashMap & Delayed> extends AbstractBounded<E> {

		public boolean boundedOperation(E myE) {
			return false;
		}
	}


	private static interface GenericParameter<T> {

		T getFor(Class<T> cls);
	}


	private static class StringGenericParameter implements GenericParameter<String> {

		public String getFor(Class<String> cls) {
			return "foo";
		}

		public String getFor(Integer integer) {
			return "foo";
		}
	}


	private static class StringList implements List<String> {

		public int size() {
			throw new UnsupportedOperationException();
		}

		public boolean isEmpty() {
			throw new UnsupportedOperationException();
		}

		public boolean contains(Object o) {
			throw new UnsupportedOperationException();
		}

		public Iterator<String> iterator() {
			throw new UnsupportedOperationException();
		}

		public Object[] toArray() {
			throw new UnsupportedOperationException();
		}

		public <T> T[] toArray(T[] a) {
			throw new UnsupportedOperationException();
		}

		public boolean add(String o) {
			throw new UnsupportedOperationException();
		}

		public boolean remove(Object o) {
			throw new UnsupportedOperationException();
		}

		public boolean containsAll(Collection<?> c) {
			throw new UnsupportedOperationException();
		}

		public boolean addAll(Collection<? extends String> c) {
			throw new UnsupportedOperationException();
		}

		public boolean addAll(int index, Collection<? extends String> c) {
			throw new UnsupportedOperationException();
		}

		public boolean removeAll(Collection<?> c) {
			throw new UnsupportedOperationException();
		}

		public boolean retainAll(Collection<?> c) {
			throw new UnsupportedOperationException();
		}

		public void clear() {
			throw new UnsupportedOperationException();
		}

		public String get(int index) {
			throw new UnsupportedOperationException();
		}

		public String set(int index, String element) {
			throw new UnsupportedOperationException();
		}

		public void add(int index, String element) {
			throw new UnsupportedOperationException();
		}

		public String remove(int index) {
			throw new UnsupportedOperationException();
		}

		public int indexOf(Object o) {
			throw new UnsupportedOperationException();
		}

		public int lastIndexOf(Object o) {
			throw new UnsupportedOperationException();
		}

		public ListIterator<String> listIterator() {
			throw new UnsupportedOperationException();
		}

		public ListIterator<String> listIterator(int index) {
			throw new UnsupportedOperationException();
		}

		public List<String> subList(int fromIndex, int toIndex) {
			throw new UnsupportedOperationException();
		}
	}


	public interface Event {

		int getPriority();
	}


	public class GenericEvent implements Event {

		private int priority;

		public int getPriority() {
			return priority;
		}

		/**
		 * Constructor that takes an event priority
		 */
		public GenericEvent(int priority) {
			this.priority = priority;
		}

		/**
		 * Default Constructor
		 */
		public GenericEvent() {
		}
	}


	public interface UserInitiatedEvent {

		//public Session getInitiatorSession();
	}


	public abstract class BaseUserInitiatedEvent extends GenericEvent implements UserInitiatedEvent {

	}


	public class MessageEvent extends BaseUserInitiatedEvent {

	}


	public interface Channel<E extends Event> {

		void send(E event);

		void subscribe(final Receiver<E> receiver, Class<E> event);

		void unsubscribe(final Receiver<E> receiver, Class<E> event);
	}


	public interface Broadcaster {

	}


	public interface EventBroadcaster extends Broadcaster {

		public void subscribe();

		public void unsubscribe();

		public void setChannel(Channel channel);
	}


	public class GenericBroadcasterImpl implements Broadcaster {

	}


	public abstract class GenericEventBroadcasterImpl<T extends Event> extends GenericBroadcasterImpl
					implements EventBroadcaster, BeanNameAware {

		private Class<T>[] subscribingEvents;

		private Channel<T> channel;

		/**
		 * Abstract method to retrieve instance of subclass
		 *
		 * @return receiver instance
		 */
		public abstract Receiver<T> getInstance();

		public void setChannel(Channel channel) {
			this.channel = channel;
		}

		private String beanName;

		public void setBeanName(String name) {
			this.beanName = name;
		}

		public void subscribe() {

		}

		public void unsubscribe() {

		}

		public GenericEventBroadcasterImpl(Class<? extends T>... events) {

		}
	}


	public interface Receiver<E extends Event> {

		void receive(E event);
	}


	public interface MessageBroadcaster extends Receiver<MessageEvent> {

	}


	public class RemovedMessageEvent extends MessageEvent {

	}


	public class NewMessageEvent extends MessageEvent {

	}


	public class ModifiedMessageEvent extends MessageEvent {

	}


	public class MessageBroadcasterImpl extends GenericEventBroadcasterImpl<MessageEvent>
					implements MessageBroadcaster {

		public MessageBroadcasterImpl() {
			super(NewMessageEvent.class);
		}

		public void receive(MessageEvent event) {
			throw new UnsupportedOperationException("should not be called, use subclassed events");
		}

		public void receive(NewMessageEvent event) {
		}

		@Override
		public Receiver<MessageEvent> getInstance() {
			return null;
		}

		public void receive(RemovedMessageEvent event) {
		}

		public void receive(ModifiedMessageEvent event) {
		}
	}


	//-----------------------------
	// SPR-2454 Test Classes
	//-----------------------------

	public interface SimpleGenericRepository<T> {

		public Class<T> getPersistentClass();


		List<T> findByQuery();


		List<T> findAll();


		T refresh(T entity);


		T saveOrUpdate(T entity);


		void delete(Collection<T> entities);
	}


	public interface RepositoryRegistry {

		<T> SimpleGenericRepository<T> getFor(Class<T> entityType);
	}


	public class SettableRepositoryRegistry<R extends SimpleGenericRepository<?>>
					implements RepositoryRegistry, InitializingBean {

		protected void injectInto(R rep) {
		}

		public void register(R rep) {
		}

		public void register(R... reps) {
		}

		public void setRepos(R... reps) {
		}

		public <T> SimpleGenericRepository<T> getFor(Class<T> entityType) {
			return null;
		}

		public void afterPropertiesSet() throws Exception {
		}
	}


	public interface ConvenientGenericRepository<T, ID extends Serializable> extends SimpleGenericRepository<T> {

		T findById(ID id, boolean lock);

		List<T> findByExample(T exampleInstance);

		void delete(ID id);

		void delete(T entity);
	}


	public class GenericHibernateRepository<T, ID extends Serializable> extends HibernateDaoSupport
					implements ConvenientGenericRepository<T, ID> {

		/**
		 * @param c Mandatory. The domain class this repository is responsible for.
		 */
		// Since it is impossible to determine the actual type of a type
		// parameter (!), we resort to requiring the caller to provide the
		// actual type as parameter, too.
		// Not set in a constructor to enable easy CGLIB-proxying (passing
		// constructor arguments to Spring AOP proxies is quite cumbersome).
		public void setPersistentClass(Class<T> c) {
		}

		public Class<T> getPersistentClass() {
			return null;
		}

		public T findById(ID id, boolean lock) {
			return null;
		}

		public List<T> findAll() {
			return null;
		}

		public List<T> findByExample(T exampleInstance) {
			return null;
		}

		public List<T> findByQuery() {
			return null;
		}

		public T saveOrUpdate(T entity) {
			return null;
		}

		public void delete(T entity) {
		}

		public T refresh(T entity) {
			return null;
		}

		public void delete(ID id) {
		}

		public void delete(Collection<T> entities) {
		}
	}


	public class HibernateRepositoryRegistry extends SettableRepositoryRegistry<GenericHibernateRepository<?, ?>> {

		public void injectInto(GenericHibernateRepository<?, ?> rep) {
		}

		public <T> GenericHibernateRepository<T, ?> getFor(Class<T> entityType) {
			return null;
		}
	}


	//-------------------
	// SPR-2603 classes
	//-------------------

	public interface Homer<E> {

		void foo(E e);
	}


	public class MyHomer<T extends Bounded<T>, L extends T> implements Homer<L> {

		public void foo(L t) {
			throw new UnsupportedOperationException();
		}
	}


	public class YourHomer<T extends AbstractBounded<T>, L extends T> extends MyHomer<T, L> {

		public void foo(L t) {
			throw new UnsupportedOperationException();
		}
	}


	public interface GenericDao<T> {

		public void saveOrUpdate(T t);
	}


	public interface ConvenienceGenericDao<T> extends GenericDao<T> {
	}


	public class GenericSqlMapDao<T extends Serializable> implements ConvenienceGenericDao<T> {

		public void saveOrUpdate(T t) {
			throw new UnsupportedOperationException();
		}
	}


	public class GenericSqlMapIntegerDao<T extends Integer> extends GenericSqlMapDao<T> {

		public void saveOrUpdate(T t) {
		}
	}


	public class Permission {
	}


	public class User {
	}


	public interface UserDao {

		@Transactional
		void save(User user);

		@Transactional
		void save(Permission perm);
	}


	public abstract class AbstractDao<T> {

		public void save(T t) {
		}

		public void saveVararg(T t, Object... args) {
		}
	}


	public class UserDaoImpl extends AbstractDao<User> implements UserDao {

		public void save(Permission perm) {
		}

		public void saveVararg(User user, Object... args) {
		}
	}


	public interface DaoInterface<T,P> {
			T get(P id);
	}


	public abstract class BusinessGenericDao<T, PK extends Serializable> implements DaoInterface<T, PK> {

		public void save(T object) {
		}
	}


	public class Business<T> {

	}


	public class BusinessDao extends BusinessGenericDao<Business<?>, Long> {

    public void save(Business<?> business) {
    }

		public Business<?> get(Long id) {
			return null;
		}

		public Business<?> get(String code) {
			return null;
		}
	}


	//-------------------
	// SPR-3304 classes
	//-------------------

	private static class MegaEvent {

	}


	private static class MegaMessageEvent extends MegaEvent {

	}


	private static class NewMegaMessageEvent extends MegaEvent {

	}


	private static class ModifiedMegaMessageEvent extends MegaEvent {

	}


	private static interface MegaReceiver<E extends MegaEvent> {

		void receive(E event);
	}


	private static interface MegaMessageProducer extends MegaReceiver<MegaMessageEvent> {

	}


	private static class Other<S,E> {

	}


	private static class MegaMessageProducerImpl extends Other<Long, String> implements MegaMessageProducer {

		public void receive(NewMegaMessageEvent event) {
			throw new UnsupportedOperationException();
		}

		public void receive(ModifiedMegaMessageEvent event) {
			throw new UnsupportedOperationException();
		}

		public void receive(MegaMessageEvent event) {
			throw new UnsupportedOperationException();
		}
	}


	//-------------------
	// SPR-3357 classes
	//-------------------

	private static class DomainObjectSuper {

	}


	private static class DomainObjectExtendsSuper extends DomainObjectSuper {

	}


	private interface IGenericInterface<D extends DomainObjectSuper> {

		<T> void doSomething(final D domainObject, final T value);
	}


	private static abstract class AbstractImplementsInterface<D extends DomainObjectSuper> implements IGenericInterface<D> {

		public <T> void doSomething(D domainObject, T value) {
		}

		public void anotherBaseMethod() {
		}
	}


	private static class ExtendsAbstractImplementsInterface extends AbstractImplementsInterface<DomainObjectExtendsSuper> {

		@Override
		public <T> void doSomething(DomainObjectExtendsSuper domainObject, T value) {
			super.doSomething(domainObject, value);
		}
	}


	//-------------------
	// SPR-3485 classes
	//-------------------

	private static class ParameterType implements Serializable {

	}


	private static class AbstractDomainObject<P extends Serializable, R> {

		public R method1(P p) {
			return null;
		}

		public void method2(P p, R r) {
		}
	}


	private static class DomainObject extends AbstractDomainObject<ParameterType, byte[]> {

		public byte[] method1(ParameterType p) {
			return super.method1(p);
		}

		public void method2(ParameterType p, byte[] r) {
			super.method2(p, r);
		}
	}


	//-------------------
	// SPR-3534 classes
	//-------------------

	public interface SearchProvider<RETURN_TYPE, CONDITIONS_TYPE> {

		Collection<RETURN_TYPE> findBy(CONDITIONS_TYPE conditions);
	}


	public static class SearchConditions {

	}


	public interface IExternalMessageProvider<S extends ExternalMessage, T extends ExternalMessageSearchConditions>
			extends SearchProvider<S, T> {
	}


	public static class ExternalMessage {

	}


	public static class ExternalMessageSearchConditions<T extends ExternalMessage> extends SearchConditions {

	}


	public static class ExternalMessageProvider<S extends ExternalMessage, T extends ExternalMessageSearchConditions<S>>
			implements IExternalMessageProvider<S, T> {

		public Collection<S> findBy(T conditions) {
			return null;
		}
	}


	public static class EmailMessage extends ExternalMessage {

	}


	public static class EmailSearchConditions extends ExternalMessageSearchConditions<EmailMessage> {

	}


	public static class EmailMessageProvider extends ExternalMessageProvider<EmailMessage, EmailSearchConditions> { }


	public static class TestEmailProvider extends EmailMessageProvider {

		public Collection<EmailMessage> findBy(EmailSearchConditions conditions) {
			return null;
		}
	}

}
