/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.service.importer.support;

import java.awt.Polygon;
import java.awt.Shape;

import junit.framework.TestCase;

import org.aopalliance.aop.Advice;
import org.eclipse.gemini.blueprint.TestUtils;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.InfrastructureOsgiProxyAdvice;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceDynamicInterceptor;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceInvoker;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceStaticInterceptor;
import org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.InfrastructureProxy;
import org.eclipse.gemini.blueprint.mock.MockBundleContext;
import org.eclipse.gemini.blueprint.mock.MockServiceReference;
import org.springframework.util.ObjectUtils;

/**
 * @author Costin Leau
 * 
 */
public class OsgiServiceProxyEqualityTest extends TestCase {

	private Object target;

	private MockBundleContext bundleContext;

	private ClassLoader classLoader;


	/**
	 * Simple interface which declares equals.
	 * 
	 * @author Costin Leau
	 * 
	 */
	public static interface InterfaceWithEquals {

		int getCount();

		boolean equals(Object other);

		Object doSmth();
	}

	public static class Implementor implements InterfaceWithEquals {

		private int count = 0;


		public Implementor(int count) {
			this.count = count;
		}

		public Implementor() {
		}

		public Object doSmth() {
			return ObjectUtils.getIdentityHexString(this);
		}

		public int getCount() {
			return count;
		}

		public boolean equals(Object other) {
			if (this == other)
				return true;

			if (other instanceof InterfaceWithEquals) {
				InterfaceWithEquals oth = (InterfaceWithEquals) other;
				return getCount() == oth.getCount();
			}

			return false;
		}
	}


	private ServiceReference ref;


	protected void setUp() throws Exception {
		ref = new MockServiceReference();
		bundleContext = new MockBundleContext() {

			public ServiceReference getServiceReference(String clazz) {
				return ref;
			}

			public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
				return new ServiceReference[] { ref };
			}
		};

		classLoader = getClass().getClassLoader();
	}

	protected void tearDown() throws Exception {
		target = null;
		bundleContext = null;
	}

	private Object createProxy(Object target, Class<?> intf, Advice[] advices) {
		ProxyFactory factory = new ProxyFactory();
		factory.addInterface(intf);
		if (advices != null)
			for (int i = 0; i < advices.length; i++) {
				factory.addAdvice(advices[0]);
			}

		factory.setTarget(target);
		return factory.getProxy();
	}

	private ServiceDynamicInterceptor createInterceptorWServiceRequired() {
		ServiceDynamicInterceptor interceptor = new ServiceDynamicInterceptor(bundleContext, null, null, classLoader);
		interceptor.setMandatoryService(true);
		interceptor.setProxy(new Object());
		interceptor.setServiceImporter(new Object());

		interceptor.afterPropertiesSet();
		return interceptor;
	}

	private ServiceDynamicInterceptor createInterceptorWOServiceRequired() {
		ServiceDynamicInterceptor interceptor = new ServiceDynamicInterceptor(bundleContext, null, null, classLoader);
		interceptor.setMandatoryService(false);
		interceptor.setProxy(new Object());
		interceptor.setServiceImporter(new Object());
		interceptor.afterPropertiesSet();
		return interceptor;

	}

	// TESTS on target W/O an equals defined on it

	public void testSameInterceptorEquality() throws Exception {
		target = new Polygon();

		Advice interceptor = createInterceptorWOServiceRequired();

		Object proxyA = createProxy(target, Shape.class, new Advice[] { interceptor });
		Object proxyB = createProxy(target, Shape.class, new Advice[] { interceptor });

		assertFalse(proxyA == proxyB);
		assertEquals(proxyA, proxyB);
	}

	public void testEqualsInterceptorsEquality() throws Exception {

		target = new Polygon();

		Advice interceptorA = createInterceptorWOServiceRequired();
		Advice interceptorB = createInterceptorWOServiceRequired();

		Object proxyA = createProxy(target, Shape.class, new Advice[] { interceptorA });
		Object proxyB = createProxy(target, Shape.class, new Advice[] { interceptorB });

		assertFalse(proxyA == proxyB);
		assertEquals(proxyA, proxyB);
		assertEquals(interceptorA, interceptorB);
	}

	public void testMultipleInterceptorEquality() throws Exception {
		target = new Polygon();

		Advice interceptorA1 = createInterceptorWOServiceRequired();

		Advice interceptorA2 = new LocalBundleContextAdvice(bundleContext);
		Advice interceptorA3 = new ServiceTCCLInterceptor(null);

		Advice interceptorB1 = createInterceptorWOServiceRequired();
		Advice interceptorB2 = new LocalBundleContextAdvice(bundleContext);
		Advice interceptorB3 = new ServiceTCCLInterceptor(null);

		Object proxyA = createProxy(target, Shape.class, new Advice[] { interceptorA1, interceptorA2, interceptorA3 });
		Object proxyB = createProxy(target, Shape.class, new Advice[] { interceptorB1, interceptorB2, interceptorB3 });

		assertFalse(proxyA == proxyB);
		assertEquals(interceptorA1, interceptorB1);
		assertEquals(interceptorA2, interceptorB2);
		assertEquals(interceptorA3, interceptorB3);

		assertEquals(proxyA, proxyB);
	}

	//
	// TESTS on object with an EQUAL defined on it
	//
	public void testDifferentInterceptorsButTargetHasEquals() throws Exception {
		target = new Implementor();
		bundleContext = new MockBundleContext() {

			public Object getService(ServiceReference reference) {
				return target;
			}
		};

		ServiceDynamicInterceptor interceptorA1 = createInterceptorWServiceRequired();
		interceptorA1.setRetryTimeout(10);

		Advice interceptorB1 = new ServiceStaticInterceptor(bundleContext, new MockServiceReference());

		InterfaceWithEquals proxyA = (InterfaceWithEquals) createProxy(target, InterfaceWithEquals.class,
			new Advice[] { interceptorA1 });
		InterfaceWithEquals proxyB = (InterfaceWithEquals) createProxy(target, InterfaceWithEquals.class,
			new Advice[] { interceptorB1 });

		assertFalse(proxyA == proxyB);
		assertFalse("interceptors should not be equal", interceptorA1.equals(interceptorB1));

		assertEquals(((InterfaceWithEquals) target).doSmth(), proxyA.doSmth());
		assertEquals(((InterfaceWithEquals) target).doSmth(), proxyB.doSmth());

		assertEquals(proxyA, proxyB);
	}

	public void testDifferentProxySetupButTargetHasEquals() throws Exception {
		target = new Implementor();

		Advice interceptorA1 = new LocalBundleContextAdvice(bundleContext);
		Advice interceptorB1 = new ServiceTCCLInterceptor(null);

		InterfaceWithEquals proxyA = (InterfaceWithEquals) createProxy(target, InterfaceWithEquals.class,
			new Advice[] { interceptorA1 });
		InterfaceWithEquals proxyB = (InterfaceWithEquals) createProxy(target, InterfaceWithEquals.class,
			new Advice[] { interceptorB1 });

		assertFalse(proxyA == proxyB);
		assertFalse("interceptors should not be equal", interceptorA1.equals(interceptorB1));

		assertEquals(((InterfaceWithEquals) target).doSmth(), proxyA.doSmth());
		assertEquals(((InterfaceWithEquals) target).doSmth(), proxyB.doSmth());

		assertEquals(proxyA, proxyB);
	}

	public void testSpringInfrastructureProxyOnImportersWithTheSameRef() throws Exception {
		Object service = new Object();
		ServiceInvoker invokerA = new ServiceStaticInterceptor(createObjectTrackingBundleContext(service), ref);
		ServiceInvoker invokerB = new ServiceStaticInterceptor(createObjectTrackingBundleContext(service), ref);
		InfrastructureProxy proxyA = new InfrastructureOsgiProxyAdvice(invokerA);
		InfrastructureProxy proxyB = new InfrastructureOsgiProxyAdvice(invokerB);

		// though this is not normal, we want the interceptors to be different to make sure the wrapped object
		// gets properly delegated
		assertFalse("invokers should not be equal (they have different bundle contexts)", invokerA.equals(invokerB));
		assertFalse("proxies should not be equal", proxyA.equals(proxyB));
		assertSame(proxyA.getWrappedObject(), proxyB.getWrappedObject());
	}

	public void testSpringInfrastructureProxyOnImportersWithDifferentRefs() throws Exception {
		Object service = new Object();
		BundleContext ctx = createObjectTrackingBundleContext(service);
		ServiceInvoker invokerA = new ServiceStaticInterceptor(ctx, new MockServiceReference());
		ServiceInvoker invokerB = new ServiceStaticInterceptor(ctx, new MockServiceReference());
		InfrastructureProxy proxyA = new InfrastructureOsgiProxyAdvice(invokerA);
		InfrastructureProxy proxyB = new InfrastructureOsgiProxyAdvice(invokerB);

		assertFalse("invokers should not be equal (they have different service references)", invokerA.equals(invokerB));
		assertFalse("proxies should not be equal", proxyA.equals(proxyB));
		assertFalse("target objects should not be equal", proxyA.getWrappedObject().equals(proxyB.getWrappedObject()));
	}

	public void testNakedTargetPropertyReturnedByTheInfrastructureProxy() throws Exception {
		Object service = new Object();
		ServiceInvoker invoker = new ServiceStaticInterceptor(createObjectTrackingBundleContext(service), ref);
		InfrastructureProxy proxy = new InfrastructureOsgiProxyAdvice(invoker);
		assertSame(TestUtils.invokeMethod(invoker, "getTarget", null), proxy.getWrappedObject());
		assertSame(service, proxy.getWrappedObject());
	}

	public void testEqualityBetweenInfrastructureProxies() throws Exception {
		Advice interceptorA1 = new InfrastructureOsgiProxyAdvice(new ServiceStaticInterceptor(bundleContext, ref));
		Advice interceptorB1 = new InfrastructureOsgiProxyAdvice(new ServiceStaticInterceptor(bundleContext, ref));

		assertEquals("interceptors should be equal", interceptorA1, interceptorB1);
	}

	public void testNonEqualityBetweenInfrastructureProxies() throws Exception {
		Advice interceptorA1 = new InfrastructureOsgiProxyAdvice(new ServiceStaticInterceptor(bundleContext, ref));
		Advice interceptorB1 = new InfrastructureOsgiProxyAdvice(createInterceptorWOServiceRequired());

		assertFalse("interceptors should not be equal", interceptorA1.equals(interceptorB1));
	}

	private BundleContext createObjectTrackingBundleContext(final Object trackedObject) {
		return new MockBundleContext() {

			public ServiceReference getServiceReference(String clazz) {
				return ref;
			}

			public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
				return new ServiceReference[] { ref };
			}

			public Object getService(ServiceReference reference) {
				return (reference.equals(ref) ? trackedObject : super.getService(reference));
			}
		};
	}

}
