/*
 * 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.
 */

package org.apache.harmony.luni.tests.java.lang;

import java.io.InputStream;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.SecurityPermission;

import junit.framework.TestCase;

public class ClassLoaderTest extends TestCase {

    public static volatile int flag;

    /**
     * Tests that Classloader.defineClass() assigns appropriate
     * default domains to the defined classes.
     */
    public void test_defineClass_defaultDomain() throws Exception {
        // Regression for HARMONY-765
        DynamicPolicy plc = new DynamicPolicy();
        Policy back = Policy.getPolicy();
        try {
            Policy.setPolicy(plc);

            Class<?> a = new Ldr().define();

            Permission p = new SecurityPermission("abc");
            assertFalse("impossible! misconfiguration?", a.getProtectionDomain().implies(p));

            plc.pc = p.newPermissionCollection();
            plc.pc.add(p);
            assertTrue("default domain is not dynamic", a.getProtectionDomain().implies(p));
        } finally {
            Policy.setPolicy(back);
        }
    }

    static class SyncTestClassLoader extends ClassLoader {
        Object lock;
        volatile int numFindClassCalled;

        SyncTestClassLoader(Object o) {
            this.lock = o;
            numFindClassCalled = 0;
        }

        /*
         * Byte array of bytecode equivalent to the following source code:
         * public class TestClass {
         * }
         */
        private byte[] classData = new byte[] {
            -54, -2, -70, -66, 0, 0, 0, 49, 0, 13,
            10, 0, 3, 0, 10, 7, 0, 11, 7, 0,
            12, 1, 0, 6, 60, 105, 110, 105, 116, 62,
            1, 0, 3, 40, 41, 86, 1, 0, 4, 67,
            111, 100, 101, 1, 0, 15, 76, 105, 110, 101,
            78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
            101, 1, 0, 10, 83, 111, 117, 114, 99, 101,
            70, 105, 108, 101, 1, 0, 14, 84, 101, 115,
            116, 67, 108, 97, 115, 115, 46, 106, 97, 118,
            97, 12, 0, 4, 0, 5, 1, 0, 9, 84,
            101, 115, 116, 67, 108, 97, 115, 115, 1, 0,
            16, 106, 97, 118, 97, 47, 108, 97, 110, 103,
            47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
            2, 0, 3, 0, 0, 0, 0, 0, 1, 0,
            1, 0, 4, 0, 5, 0, 1, 0, 6, 0,
            0, 0, 29, 0, 1, 0, 1, 0, 0, 0,
            5, 42, -73, 0, 1, -79, 0, 0, 0, 1,
            0, 7, 0, 0, 0, 6, 0, 1, 0, 0,
            0, 1, 0, 1, 0, 8, 0, 0, 0, 2,
            0, 9 };

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                while (flag != 2) {
                    synchronized (lock) {
                        lock.wait();
                    }
                }
            } catch (InterruptedException ie) {}

            if (name.equals("TestClass")) {
                numFindClassCalled++;
                return defineClass(null, classData, 0, classData.length);
            }
            throw new ClassNotFoundException("Class " + name + " not found.");
        }
    }

    static class SyncLoadTestThread extends Thread {
        volatile boolean started;
        ClassLoader cl;
        Class<?> cls;

        SyncLoadTestThread(ClassLoader cl) {
            this.cl = cl;
        }

        @Override
        public void run() {
            try {
                started = true;
                cls = Class.forName("TestClass", false, cl);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * Regression test for HARMONY-1939:
     * 2 threads simultaneously run Class.forName() method for the same classname
     * and the same classloader. It is expected that both threads succeed but
     * class must be defined just once.
     */
    public void test_loadClass_concurrentLoad() throws Exception
    {
        Object lock = new Object();
        SyncTestClassLoader cl = new SyncTestClassLoader(lock);
        SyncLoadTestThread tt1 = new SyncLoadTestThread(cl);
        SyncLoadTestThread tt2 = new SyncLoadTestThread(cl);
        flag = 1;
        tt1.start();
        tt2.start();

        while (!tt1.started && !tt2.started) {
            Thread.sleep(100);
        }

        flag = 2;
        synchronized (lock) {
            lock.notifyAll();
        }
        tt1.join();
        tt2.join();

        assertSame("Bad or redefined class", tt1.cls, tt2.cls);
        assertEquals("Both threads tried to define class", 1, cl.numFindClassCalled);
    }

    /**
     * @tests java.lang.ClassLoader#getResource(java.lang.String)
     */
    public void test_getResourceLjava_lang_String() {
        // Test for method java.net.URL
        // java.lang.ClassLoader.getResource(java.lang.String)
        java.net.URL u = ClassLoader.getSystemClassLoader().getResource("hyts_Foo.c");
        assertNotNull("Unable to find resource", u);
        java.io.InputStream is = null;
        try {
            is = u.openStream();
            assertNotNull("Resource returned is invalid", is);
            is.close();
        } catch (java.io.IOException e) {
            fail("IOException getting stream for resource : " + e.getMessage());
        }
    }

    /**
     * @tests java.lang.ClassLoader#getResourceAsStream(java.lang.String)
     */
    public void test_getResourceAsStreamLjava_lang_String() {
        // Test for method java.io.InputStream
        // java.lang.ClassLoader.getResourceAsStream(java.lang.String)
        // Need better test...

        java.io.InputStream is = null;
        assertNotNull("Failed to find resource: hyts_Foo.c", (is = ClassLoader
                .getSystemClassLoader().getResourceAsStream("hyts_Foo.c")));
        try {
            is.close();
        } catch (java.io.IOException e) {
            fail("Exception during getResourceAsStream: " + e.toString());
        }
    }

    /**
     * @tests java.lang.ClassLoader#getSystemClassLoader()
     */
    public void test_getSystemClassLoader() {
        // Test for method java.lang.ClassLoader
        // java.lang.ClassLoader.getSystemClassLoader()
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        java.io.InputStream is = cl.getResourceAsStream("hyts_Foo.c");
        assertNotNull("Failed to find resource from system classpath", is);
        try {
            is.close();
        } catch (java.io.IOException e) {
        }

    }

    /**
     * @tests java.lang.ClassLoader#getSystemResource(java.lang.String)
     */
    public void test_getSystemResourceLjava_lang_String() {
        // Test for method java.net.URL
        // java.lang.ClassLoader.getSystemResource(java.lang.String)
        // Need better test...
        assertNotNull("Failed to find resource: hyts_Foo.c", ClassLoader
                .getSystemResource("hyts_Foo.c"));
    }


    //Regression Test for JIRA-2047
    public void test_getResourceAsStream_withSharpChar() throws Exception {
		InputStream in = this.getClass().getClassLoader().getResourceAsStream(
				ClassTest.FILENAME);
		assertNotNull(in);
		in.close();
	}
}

class DynamicPolicy extends Policy {

    public PermissionCollection pc;

    @Override
    public PermissionCollection getPermissions(ProtectionDomain pd) {
        return pc;
    }

    @Override
    public PermissionCollection getPermissions(CodeSource codesource) {
        return pc;
    }

    @Override
    public void refresh() {
    }
}

class A {
}

class Ldr extends ClassLoader {
    @SuppressWarnings("deprecation")
    public Class<?> define() throws Exception {
        Package p = getClass().getPackage();
        // Class loader paths use '/' character as separator
        String path = p == null ? "" : p.getName().replace('.', '/') + '/';
        InputStream is = getResourceAsStream(path + "A.class");
        byte[] buf = new byte[512];
        int len = is.read(buf);
        return defineClass(buf, 0, len);
    }
}
