/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */
/* 
 *
 * @summary functional test for RMIClassLoader.loadProxyClass; test
 * ensures that the default RMI class loader provider implements
 * RMIClassLoader.loadProxyClass correctly.
 *
 * 
 *
 * @library ../../../../../testlibrary
 * @build TestLibrary FnnClass FnnUnmarshal LoadProxyClasses NonpublicInterface
 * @build NonpublicInterface1 PublicInterface PublicInterface1
 * @run main/othervm/policy=security.policy LoadProxyClasses
 */

import java.rmi.server.RMIClassLoader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.MarshalledObject;
import java.net.URL;
import java.net.URLClassLoader;
import java.io.Serializable;
import java.io.IOException;

import java.util.Arrays;
import java.util.zip.Checksum;

/** 
 *  Invokes RMIClassLoader.loadProxyClass() to load a proxy class with
 *  multiple interfaces using using RMI class unmarshalling.  Test is
 *  composed of cases which each unmarshal a proxy class in a
 *  different environment.  All of the cases create needed class
 *  loaders, load appropriate interfaces, create a proxy class that
 *  implements those interfaces, create a marshalled object from that
 *  proxy class, and finally call .get() on that object.  Get of the
 *  object should pass in some cases and fail in others.
 *  
 *  1. Nonpublic interface loaded from the parent of the First
 *  Non-Null class Loader on the execution stack (FNNL).  Public
 *  interface loaded from grandparent of FNNL parent. Proxy class must
 *  be defined in non-null FNNL parent. Should succeed.
 * 
 *  2. Nonpublic interface (java.util.zip.ZipConstants) and public
 *  interface (java.util.zip.CheckSum) loaded from bootclasspath,
 *  proxy class defined in null/boot class loader.  Should succeed.
 *  
 *  3. Public interface classes loaded in FNNL are also available in
 *  RMI loader parent.  FNNL is grandparent of RMI loader. Proxy class
 *  must be defined in RMI class loader. Should succeed. public
 *  interface must be defined in FNNL.
 * 
 *  4. Non-public interfaces have multiple class loaders. Should fail
 *  with a LinkageError.
 *  
 *  5. Interface classes loaded from RMI class loader. Proxy class
 *  defined in RMI class loader.
 * 
 *  6. Not all interfaces classes can be loaded from a single class
 *  loader; should fail with ClassNotFoundException.  All interface
 *  classes will exist (but not all interfaces will be available from
 *  one class loader).
 *
 *  7. prove that proxy loader has correct annotation.
 *
 *  8. REMIND: may want to add a case where the FNNL is null (This
 *  would be for class unmarshalling in the implemntation of a remote
 *  method invocation).
 */
public class LoadProxyClasses {

    private static URL publicUrl = null;

    public static boolean boomerangSemantics = false;
    
    public static void main(String[] args) {
	try {
	    System.err.println("\nFunctional test to verify that RMI " +
			       "loads proxy classes correctly\n");

	    /* install proxy interfaces */
	    publicUrl =
		TestLibrary.installClassInCodebase("PublicInterface",
						   "public");
	    URL publicUrl1 =
		TestLibrary.installClassInCodebase("PublicInterface1",
						   "public1");
	    URL nonpublicUrl =
		TestLibrary.installClassInCodebase("NonpublicInterface",
						   "nonpublic", false);
	    URL nonpublicUrl1 =
		TestLibrary.installClassInCodebase("NonpublicInterface1",
						   "nonpublic1", false);
	    URL bothNonpublicUrl =
		TestLibrary.installClassInCodebase("NonpublicInterface",
						   "bothNonpublic");
	    TestLibrary.installClassInCodebase("NonpublicInterface1",
					       "bothNonpublic");
	    URL fnnUrl =
		TestLibrary.installClassInCodebase("FnnClass", "fnn");

	    TestLibrary.suggestSecurityManager(null);


	    /* Case 1 */
	    ClassLoader grandParentPublic =
		new URLClassLoader(new URL[] {publicUrl});
	    ClassLoader parentNonpublic =
		new URLClassLoader(new URL[] {nonpublicUrl},
				   grandParentPublic);
	    URLClassLoader fnnLoader1 =
		new URLClassLoader(new URL[] {fnnUrl}, parentNonpublic);
	    
	    Class nonpublicInterface =
		fnnLoader1.loadClass("NonpublicInterface");
	    Class publicInterface =
		fnnLoader1.loadClass("PublicInterface");
	    
	    Proxy proxy1 = (Proxy) Proxy.newProxyInstance(parentNonpublic,
		new Class[] {nonpublicInterface, publicInterface},
		new TestInvocationHandler());
	    unmarshalProxyClass(proxy1, fnnLoader1, parentNonpublic, 1, null);



	    /* Case 2 */
	    Class zipConstantsClass =
		Class.forName("java.util.zip.ZipConstants");
	    URLClassLoader fnnLoader2 =
		new URLClassLoader(new URL[] {fnnUrl});
	    Proxy proxy2 = (Proxy) Proxy.newProxyInstance(null,
		new Class[] {zipConstantsClass, Checksum.class},
		new TestInvocationHandler());
	    unmarshalProxyClass(proxy2, fnnLoader2,
				(ClassLoader) null, 2, null);



	    /* Case 3 */
	    Thread currentThread = Thread.currentThread();
	    ClassLoader fnnLoader3 = new URLClassLoader(
		new URL[] {publicUrl, fnnUrl});
	    ClassLoader newCtxLoader =
		new URLClassLoader(new URL[] {publicUrl}, fnnLoader3);
	    Class publicInterface3 =
		fnnLoader3.loadClass("PublicInterface");
	    ClassLoader currentCtxLoader =
		currentThread.getContextClassLoader();
	    currentThread.setContextClassLoader(newCtxLoader);

	    Proxy proxy3 = (Proxy) Proxy.newProxyInstance(newCtxLoader,
		new Class[] {publicInterface3},
		new TestInvocationHandler());
	    
	    unmarshalProxyClass(proxy3, fnnLoader3, fnnLoader3,
		3, new Case3Checker());

	    currentThread.setContextClassLoader(currentCtxLoader);



	    /* Case 4 */
	    ClassLoader bothNonpublicLoader =
		new URLClassLoader(new URL[] {bothNonpublicUrl});
	    Class nonpublicInterface4a =
		bothNonpublicLoader.loadClass("NonpublicInterface");
	    Class nonpublicInterface4b =
		bothNonpublicLoader.loadClass("NonpublicInterface1");
	    Proxy proxy4 = (Proxy) Proxy.newProxyInstance(bothNonpublicLoader,
		new Class[] {nonpublicInterface4a, nonpublicInterface4b},
		new TestInvocationHandler());
	    
	    ClassLoader nonpublicLoaderA =
		new URLClassLoader(new URL[] {nonpublicUrl});
	    ClassLoader nonpublicLoaderB =
		new URLClassLoader(new URL[] {nonpublicUrl1}, nonpublicLoaderA);
	    currentCtxLoader =
		currentThread.getContextClassLoader();
	    currentThread.setContextClassLoader(nonpublicLoaderB);

	    IllegalAccessError illegal = null;
	    try {
		unmarshalProxyClass(proxy4, fnnLoader2, nonpublicLoaderB,
				    4, null);
	    } catch (IllegalAccessError e) {
		illegal = e;
	    }

	    if (illegal == null) {
		TestLibrary.bomb("case4: IllegalAccessError not thrown " +
				 "when multiple nonpublic interfaces have \n" +
				 "different class loaders");
	    } else {
		System.err.println("\ncase4: IllegalAccessError correctly " +
				   "thrown \n when trying to load proxy " +
				   "with multiple nonpublic interfaces in \n" +
				   "  different class loaders");
	    }
	    currentThread.setContextClassLoader(currentCtxLoader);



	    /* Case 5*/
	    ClassLoader publicLoader =
		new URLClassLoader(new URL[] {publicUrl});
	    Class publicInterface5 =
		publicLoader.loadClass("PublicInterface");
	    Proxy proxy5 = (Proxy) Proxy.newProxyInstance(publicLoader,
		new Class[] {publicInterface5},
		new TestInvocationHandler());

	    currentCtxLoader =
		currentThread.getContextClassLoader();
	    currentThread.setContextClassLoader(publicLoader);
	    unmarshalProxyClass(proxy5, fnnLoader2, publicLoader, 5,
				new Case5Checker());
	    currentThread.setContextClassLoader(currentCtxLoader);



	    /* Case 6 */
	    ClassLoader fnnLoader6 =
		new URLClassLoader(new URL[] {fnnUrl, publicUrl});
	    ClassLoader publicLoader6 =
		new URLClassLoader(new URL[] {publicUrl1}, fnnLoader6);

	    Class publicInterface6a =
		publicLoader6.loadClass("PublicInterface1");
	    Class publicInterface6b =
		fnnLoader6.loadClass("PublicInterface");
	    Proxy proxy6 = (Proxy) Proxy.newProxyInstance(publicLoader6,
		new Class[] {publicInterface6a, publicInterface6b},
		new TestInvocationHandler());
	    ClassNotFoundException cnfe = null;
	    try {
		unmarshalProxyClass(proxy6, fnnLoader6, publicLoader6, 6,
				    null);
	    } catch (ClassNotFoundException e) {
		cnfe = e;
	    }
	    if (cnfe == null) {
		TestLibrary.bomb("ClassNotFoundException not thrown " +
				 "when not all proxy interfaces could " +
				 " be found in a single class loader ");
	    } else {
		System.err.println("Case6: ClassNotFoundException " +
				   "correctly thrown when not all proxy" +
				   " interfaces could be found in a " +
				   "single class loader");
		cnfe.printStackTrace();
	    }

	    System.err.println("TEST PASSED");
	    
	} catch (Exception e) {
	    if (e instanceof RuntimeException) {
		throw (RuntimeException) e;
	    }
	    TestLibrary.bomb(e);
	}
    }


    private interface LoadChecker {
	void checkLoad(Proxy proxy, ClassLoader expectedLoader);
    }
    
    private static Proxy unmarshalProxyClass(Proxy proxy,
					     ClassLoader fnnLoader,
					     ClassLoader expectedLoader,
					     int n,
					     LoadChecker checker)
	throws ClassNotFoundException, IOException,
	       InstantiationException, IllegalAccessException
    {
	FnnUnmarshal fnnUnmarshal = (FnnUnmarshal)
		fnnLoader.loadClass("FnnClass").newInstance();	    
	Proxy unmarshalled = (Proxy)
	    fnnUnmarshal.unmarshal(new MarshalledObject(proxy));
	ClassLoader unmarshalledLoader =
	    unmarshalled.getClass().getClassLoader();

	if (checker != null) {
	    checker.checkLoad(unmarshalled, expectedLoader);
	} else {
	    if (unmarshalledLoader != expectedLoader) {
		TestLibrary.bomb("case" + n + ": proxy class not " +
				 "placed into incorrect loader: " +
				 unmarshalledLoader);
	    } else {
		System.err.println("\ncase" + n + ": proxy class correctly" +
				   " placed into expected loader: " +
				   expectedLoader);
	    }
	}
	return proxy;
    }
    
    private static class Case3Checker implements LoadChecker {
	public void checkLoad(Proxy proxy, ClassLoader expectedLoader) {
	    ClassLoader ifaceLoader =
		proxy.getClass().getInterfaces()[0].getClassLoader();
	    ClassLoader proxyLoader = proxy.getClass().getClassLoader();
	    
	    boolean proxyOk = false;
	    
	    if (boomerangSemantics) {
		ClassLoader ctxLoader =
		    Thread.currentThread().getContextClassLoader();
		if (proxyLoader == ctxLoader) {
		    proxyOk = true;
		}
	    } else if (proxyLoader.getClass().
		       getName().indexOf("sun.rmi") >= 0)
	    {
		proxyOk = true;
	    }

	    if (proxyOk) {
		System.err.println("\ncase3: proxy loaded in" +
				   " correct loader: " + proxyLoader +
				   Arrays.asList(((URLClassLoader)
						 proxyLoader).getURLs()));
	    } else {
		TestLibrary.bomb("case3: proxy class loaded in " +
				 "incorrect loader: " + proxyLoader +
				   Arrays.asList(((URLClassLoader)
						  proxyLoader).getURLs()));
	    }

	    if (ifaceLoader == expectedLoader) {
		System.err.println("case3: proxy interface loaded in" +
				   " correct loader: " + ifaceLoader);
	    } else {
		TestLibrary.bomb("public proxy interface loaded in " +
				 "incorrect loader: " + ifaceLoader);
	    }
	}
    }

    private static class Case5Checker implements LoadChecker {
	public void checkLoad(Proxy proxy, ClassLoader expectedLoader) {
	    ClassLoader proxyLoader = proxy.getClass().getClassLoader();

	    String proxyAnnotation =
		RMIClassLoader.getClassAnnotation(proxy.getClass());

	    if ((proxyAnnotation == null) ||
		!proxyAnnotation.equals(publicUrl.toString()))
	    {
		TestLibrary.bomb("proxy class had incorrect annotation: " +
				 proxyAnnotation);
	    } else {
		System.err.println("proxy class had correct annotation: " +
				   proxyAnnotation);
	    }
	    
	    boolean proxyOk = false;

	    if (boomerangSemantics) {
		ClassLoader ctxLoader =
		    Thread.currentThread().getContextClassLoader();
		if (proxyLoader == ctxLoader) {
		    proxyOk = true;
		}
	    } else if (proxyLoader.getClass().
		       getName().indexOf("sun.rmi") >= 0)
	    {
		proxyOk = true;
	    }

	    if (proxyOk) {
		System.err.println("\ncase5: proxy loaded from" +
				   " correct loader: " + proxyLoader);
	    } else {
		TestLibrary.bomb("case5: proxy interface loaded from " +
				 "incorrect loader: " + proxyLoader);
	    }
	}
    }

    private static class TestInvocationHandler
	implements InvocationHandler, Serializable
    {
	public Object invoke(Object proxy, Method method, Object[] args)
	    throws Throwable {return null;}
    }
}
