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

/**
 * @author Boris V. Kuznetsov
 * @version $Revision$
 */

package org.apache.harmony.security.tests.java.security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.Provider;
import java.security.Security;
import java.security.SecurityPermission;
import java.security.Provider.Service;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import junit.framework.TestCase;
import dalvik.annotation.KnownFailure;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;

@TestTargetClass(Provider.class)
/**
 * Tests for <code>Provider</code> constructor and methods
 * 
 */
public class ProviderTest extends TestCase {
    /*
     * Implementation note: The algorithm name ASH-1 might seem a bit strange,
     * but since the algorithms cannot be uninstalled anymore we need to make
     * sure there are not side-effects on other tests. Simply inserting SHA-1
     * destroys the existing provider infrastructure.
     */
    
    Provider[] storedProviders;
    
    Provider p;

    /*
     * @see TestCase#setUp()
     */
    protected void setUp() throws Exception {
        super.setUp();
        
        storedProviders = Security.getProviders();
        
        p = new MyProvider();
    }

    @Override
    protected void tearDown() throws Exception {
        System.setSecurityManager(null);
        
        p.remove("MessageDigest.ASH-1");
        p.remove("MessageDigest.abc");
        p.remove("Alg.Alias.MessageDigest.ASH1");
        
        for (Provider p: Security.getProviders()) {
            Security.removeProvider(p.getName());
        }

        for (Provider p: storedProviders) {
            Security.addProvider(p);
        }
        
        super.tearDown();
    }
    
    /*
     * Class under test for void Provider()
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies provider object",
        method = "Provider",
        args = {java.lang.String.class, double.class, java.lang.String.class}
    )
    public final void testProvider() {
        if (!p.getProperty("Provider.id name").equals(
                String.valueOf(p.getName()))) {
            fail("Incorrect \"Provider.id name\" value");
        }
        if (!p.getProperty("Provider.id version").equals(
                String.valueOf(p.getVersion()))) {
            fail("Incorrect \"Provider.id version\" value");
        }
        if (!p.getProperty("Provider.id info").equals(
                String.valueOf(p.getInfo()))) {
            fail("Incorrect \"Provider.id info\" value");
        }
        if (!p.getProperty("Provider.id className").equals(
                p.getClass().getName())) {
            fail("Incorrect \"Provider.id className\" value");
        }
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "clear",
        args = {}
    )
    public final void testClear() {
        p.clear();
        if (p.getProperty("MessageDigest.ASH-1") != null) {
            fail("Provider contains properties");
        }
    }
    
    @TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "",
            method = "clear",
            args = {}
        )
    public final void testClear_SecurityManager() {
        TestSecurityManager sm = new TestSecurityManager("clearProviderProperties.MyProvider");
        System.setSecurityManager(sm);
        p.clear();
        assertTrue("Provider.clear must call checkPermission with "
                + "SecurityPermission clearProviderProperties.NAME",
                sm.called);
        System.setSecurityManager(null);
    }

    /*
     * Class under test for void Provider(String, double, String)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies constructor with non null parameters",
        method = "Provider",
        args = {java.lang.String.class, double.class, java.lang.String.class}
    )
    public final void testProviderStringdoubleString() {
        Provider p = new MyProvider("Provider name", 123.456, "Provider info");
        if (!p.getName().equals("Provider name") || p.getVersion() != 123.456
                || !p.getInfo().equals("Provider info")) {
            fail("Incorrect values");
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getName",
        args = {}
    )
    public final void testGetName() {
        if (!p.getName().equals("MyProvider")) {
            fail("Incorrect provider name");
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getVersion",
        args = {}
    )
    public final void testGetVersion() {
        if (p.getVersion() != 1.0) {
            fail("Incorrect provider version");
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getInfo",
        args = {}
    )
    public final void testGetInfo() {
        if (!p.getInfo().equals("Provider for testing")) {
            fail("Incorrect provider info");
        }
    }

    /*
     * Class under test for void putAll(Map)
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "putAll",
        args = {java.util.Map.class}
    )
    public final void testPutAllMap() {
        HashMap<String, String> hm = new HashMap<String, String>();
        hm.put("MessageDigest.ASH-1", "aaa.bbb.ccc.ddd");
        hm.put("Property 1", "value 1");
        hm.put("serviceName.algName attrName", "attrValue");
        hm.put("Alg.Alias.engineClassName.aliasName", "stanbdardName");
        p.putAll(hm);
        if (!"value 1".equals(p.getProperty("Property 1").trim())
                || !"attrValue".equals(p.getProperty(
                        "serviceName.algName attrName").trim())
                || !"stanbdardName".equals(p.getProperty(
                        "Alg.Alias.engineClassName.aliasName").trim())
                || !"aaa.bbb.ccc.ddd".equals(p.getProperty(
                        "MessageDigest.ASH-1").trim())) {
            fail("Incorrect property value");
        }
    }

    /*
     * Class under test for Set entrySet()
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "entrySet",
        args = {}
    )
    public final void testEntrySet() {
        p.put("MessageDigest.ASH-256", "aaa.bbb.ccc.ddd");

        Set<Map.Entry<Object, Object>> s = p.entrySet();
        try {
            s.clear();
            fail("Must return unmodifiable set");
        } catch (UnsupportedOperationException e) {
        }

        assertEquals("Incorrect set size", 8, s.size());

        for (Iterator<Entry<Object, Object>> it = s.iterator(); it.hasNext();) {
            Entry<Object, Object> e = it.next();
            String key = (String) e.getKey();
            String val = (String) e.getValue();
            if (key.equals("MessageDigest.ASH-1")
                    && val.equals("SomeClassName")) {
                continue;
            }
            if (key.equals("Alg.Alias.MessageDigest.ASH1")
                    && val.equals("ASH-1")) {
                continue;
            }
            if (key.equals("MessageDigest.abc") && val.equals("SomeClassName")) {
                continue;
            }
            if (key.equals("Provider.id className")
                    && val.equals(p.getClass().getName())) {
                continue;
            }
            if (key.equals("Provider.id name") && val.equals("MyProvider")) {
                continue;
            }
            if (key.equals("MessageDigest.ASH-256")
                    && val.equals("aaa.bbb.ccc.ddd")) {
                continue;
            }
            if (key.equals("Provider.id version") && val.equals("1.0")) {
                continue;
            }
            if (key.equals("Provider.id info")
                    && val.equals("Provider for testing")) {
                continue;
            }
            fail("Incorrect set");
        }
    }

    /*
     * Class under test for Set keySet()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "keySet",
        args = {}
    )
    public final void testKeySet() {
        p.put("MessageDigest.ASH-256", "aaa.bbb.ccc.ddd");

        Set<Object> s = p.keySet();
        try {
            s.clear();
        } catch (UnsupportedOperationException e) {
        }
        Set<Object> s1 = p.keySet();
        if ((s == s1) || s1.isEmpty()) {
            fail("Must return unmodifiable set");
        }
        if (s1.size() != 8) {
            fail("Incorrect set size");
        }
        if (!s1.contains("MessageDigest.ASH-256")
                || !s1.contains("MessageDigest.ASH-1")
                || !s1.contains("Alg.Alias.MessageDigest.ASH1")
                || !s1.contains("MessageDigest.abc")
                || !s1.contains("Provider.id info")
                || !s1.contains("Provider.id className")
                || !s1.contains("Provider.id version")
                || !s1.contains("Provider.id name")) {
            fail("Incorrect set");
        }
    }

    /*
     * Class under test for Collection values()
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "values",
        args = {}
    )
    public final void testValues() {
        p.put("MessageDigest.ASH-256", "aaa.bbb.ccc.ddd");

        Collection<Object> c = p.values();
        try {
            c.clear();
        } catch (UnsupportedOperationException e) {
        }
        Collection<Object> c1 = p.values();
        if ((c == c1) || c1.isEmpty()) {
            fail("Must return unmodifiable set");
        }
        if (c1.size() != 8) {
            fail("Incorrect set size " + c1.size());
        }
        if (!c1.contains("MyProvider") || !c1.contains("aaa.bbb.ccc.ddd")
                || !c1.contains("Provider for testing") || !c1.contains("1.0")
                || !c1.contains("SomeClassName") || !c1.contains("ASH-1")
                || !c1.contains(p.getClass().getName())) {
            fail("Incorrect set");
        }
    }

    /*
     * Class under test for Object put(Object, Object)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "put",
        args = {java.lang.Object.class, java.lang.Object.class}
    )
    public final void testPutObjectObject() {
        p.put("MessageDigest.ASH-1", "aaa.bbb.ccc.ddd");
        p.put("Type.Algorithm", "className");
        if (!"aaa.bbb.ccc.ddd".equals(p.getProperty("MessageDigest.ASH-1")
                .trim())) {
            fail("Incorrect property value");
        }

        Set<Service> services = p.getServices();
        if (services.size() != 3) {
            fail("incorrect size");
        }
        for (Iterator<Service> it = services.iterator(); it.hasNext();) {
            Provider.Service s = it.next();
            if ("Type".equals(s.getType())
                    && "Algorithm".equals(s.getAlgorithm())
                    && "className".equals(s.getClassName())) {
                continue;
            }
            if ("MessageDigest".equals(s.getType())
                    && "ASH-1".equals(s.getAlgorithm())
                    && "aaa.bbb.ccc.ddd".equals(s.getClassName())) {
                continue;
            }
            if ("MessageDigest".equals(s.getType())
                    && "abc".equals(s.getAlgorithm())
                    && "SomeClassName".equals(s.getClassName())) {
                continue;
            }
            fail("Incorrect service");
        }
    }

    @TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "",
            method = "put",
            args = {java.lang.Object.class, java.lang.Object.class}
        )
    public final void testPutObjectObject_SecurityManager() {

        TestSecurityManager sm = new TestSecurityManager("putProviderProperty.MyProvider");
        Provider p = new MyProvider();
        System.setSecurityManager(sm);
        p.put(new Object(), new Object());
        assertTrue("Provider put must call checkPermission "
                + "SecurityPermission putProviderProperty.Name", sm.called);
        System.setSecurityManager(null);
    }

    /*
     * Class under test for Object remove(Object)
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "remove",
        args = {java.lang.Object.class}
    )
    public final void testRemoveObject() {
        Object o = p.remove("MessageDigest.ASH-1");
        if (!"SomeClassName".equals(o)) {
            fail("Incorrect return value");
        }
        if (p.getProperty("MessageDigest.ASH-1") != null) {
            fail("Provider contains properties");
        }
        if (p.getServices().size() != 1) {
            fail("Service not removed");
        }

        try {
            p.remove(null);
            fail("expected NullPointerException");
        } catch (NullPointerException e) {
            // ok
        }
    }

    /*
     * Class under test for Object remove(Object)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "remove",
        args = {java.lang.Object.class}
    )
    public final void testRemoveObject_SecurityManager() {
        TestSecurityManager sm = new TestSecurityManager(
                "removeProviderProperty.MyProvider");
        System.setSecurityManager(sm);
        p.remove(new Object());
        assertTrue("Provider.remove must check permission "
                + "SecurityPermission removeProviderProperty.NAME",
                sm.called);
        System.setSecurityManager(null);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getService",
        args = {java.lang.String.class, java.lang.String.class}
    )
    public final void testService1() {
        p.put("MessageDigest.ASH-1", "AnotherClassName");
        Provider.Service s = p.getService("MessageDigest", "ASH-1");
        if (!"AnotherClassName".equals(s.getClassName())) {
            fail("Incorrect class name " + s.getClassName());
        }
        
        try {
           p.getService("MessageDigest", null);
           fail("expected NullPointerException");
        } catch (NullPointerException e)  {
            // ok;
        }
        
        try {
            p.getService(null, "ASH-1");
            fail("expected NullPointerException");
        } catch (NullPointerException e) {
            // ok
        }
    }

    @TestTargetNew(
            level = TestLevel.COMPLETE,
            notes = "",
            method = "getService",
            args = {java.lang.String.class, java.lang.String.class}
        )
    public final void testService2() {
        Provider[] pp = Security.getProviders("MessageDigest.ASH-1");
        if (pp == null) {
            return;
        }
        Provider p2 = pp[0];
        String old = p2.getProperty("MessageDigest.ASH-1");
        p2.put("MessageDigest.ASH-1", "AnotherClassName");
        Provider.Service s = p2.getService("MessageDigest", "ASH-1");
        if (!"AnotherClassName".equals(s.getClassName())) {
            fail("Incorrect class name " + s.getClassName());
        }
        try {
            s.newInstance(null);
            fail("No expected NoSuchAlgorithmException");
        } catch (NoSuchAlgorithmException e) {
        }
    }
     

    // Regression for HARMONY-2760.
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Regression test: verifies constructor with two null parameters.",
        method = "Provider",
        args = {java.lang.String.class, double.class, java.lang.String.class}
    )
    public void testConstructor() {
        MyProvider myProvider = new MyProvider(null, 1, null);
        assertNull(myProvider.getName());
        assertNull(myProvider.getInfo());
        assertEquals("null", myProvider.getProperty("Provider.id name"));
        assertEquals("null", myProvider.getProperty("Provider.id info"));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getServices",
        args = {}
    )
    public final void testGetServices() {
        MyProvider myProvider = new MyProvider(null, 1, null);
        Set<Provider.Service> services = myProvider.getServices();
        assertEquals(0, services.size());

        Provider.Service s[] = new Provider.Service[3];

        s[0] = new Provider.Service(p, "type1", "algorithm1", "className1",
                null, null);
        s[1] = new Provider.Service(p, "type2", "algorithm2", "className2",
                null, null);
        s[2] = new Provider.Service(p, "type3", "algorithm3", "className3",
                null, null);
        myProvider.putService(s[0]);
        myProvider.putService(s[1]);
        assertEquals(2, myProvider.getNumServices());
        Set<Service> actual = myProvider.getServices();

        assertTrue(actual.contains(s[0]));
        assertTrue(actual.contains(s[1]));
        assertTrue(!actual.contains(s[2]));

        myProvider.removeService(s[1]);
        actual = myProvider.getServices();
        assertEquals(1, myProvider.getNumServices());

        assertTrue(actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(!actual.contains(s[2]));

        myProvider.putService(s[2]);
        actual = myProvider.getServices();
        assertEquals(2, myProvider.getNumServices());
        assertTrue(actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(actual.contains(s[2]));
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "putService",
        args = {java.security.Provider.Service.class}
    )
    public final void testPutService() {
        MyProvider myProvider = new MyProvider(null, 1, null);
        Provider.Service s[] = new Provider.Service[3];

        s[0] = new Provider.Service(p, "type1", "algorithm1", "className1",
                null, null);
        s[1] = new Provider.Service(p, "type2", "algorithm2", "className2",
                null, null);
        s[2] = new Provider.Service(p, "type3", "algorithm3", "className3",
                null, null);
        myProvider.putService(s[0]);
        myProvider.putService(s[1]);
        assertEquals(2, myProvider.getNumServices());
        Set<Service> actual = myProvider.getServices();

        assertTrue(actual.contains(s[0]));
        assertTrue(actual.contains(s[1]));
        assertTrue(!actual.contains(s[2]));

        myProvider.removeService(s[1]);
        assertEquals(1, myProvider.getNumServices());
        actual = myProvider.getServices();

        assertTrue(actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(!actual.contains(s[2]));

        myProvider.putService(s[2]);
        actual = myProvider.getServices();
        assertEquals(2, myProvider.getNumServices());
        assertTrue(actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(actual.contains(s[2]));

        myProvider.putService(s[2]);
        actual = myProvider.getServices();
        assertEquals(2, myProvider.getNumServices());
        assertTrue(actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(actual.contains(s[2]));

        try {
            myProvider.putService(null);
            fail("NullPointerException expected");
        } catch (NullPointerException e) {
            // expected
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "removeService",
        args = {java.security.Provider.Service.class}
    )
    public final void testRemoveService() {
        MyProvider myProvider = new MyProvider(null, 1, null);
        try {
            myProvider.removeService(null);
            fail("NullPoiterException expected");
        } catch (NullPointerException e) {
            // expected
        }

        Provider.Service s[] = new Provider.Service[3];

        s[0] = new Provider.Service(p, "type0", "algorithm0", "className0",
                null, null);
        s[1] = new Provider.Service(p, "type1", "algorithm1", "className1",
                null, null);
        s[2] = new Provider.Service(p, "type2", "algorithm2", "className2",
                null, null);

        try {
            myProvider.removeService(s[0]);
        } catch (NullPointerException e) {
            fail("Unexpected exception");
        }

        myProvider.putService(s[0]);
        myProvider.putService(s[1]);
        myProvider.putService(s[2]);
        assertEquals(3, myProvider.getNumServices());
        Set<Service> actual = myProvider.getServices();

        assertTrue(actual.contains(s[0]));
        assertTrue(actual.contains(s[1]));
        assertTrue(actual.contains(s[2]));

        myProvider.removeService(s[1]);
        assertEquals(2, myProvider.getNumServices());
        actual = myProvider.getServices();

        assertTrue(actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(actual.contains(s[2]));

        myProvider.removeService(s[0]);
        assertEquals(1, myProvider.getNumServices());
        actual = myProvider.getServices();

        assertTrue(!actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(actual.contains(s[2]));

        myProvider.removeService(s[2]);
        assertEquals(0, myProvider.getNumServices());
        actual = myProvider.getServices();

        assertTrue(!actual.contains(s[0]));
        assertTrue(!actual.contains(s[1]));
        assertTrue(!actual.contains(s[2]));

        try {
            myProvider.removeService(null);
            fail("NullPoiterException expected");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /*
     * Class under test for void load(InputStream)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "load",
        args = {java.io.InputStream.class}
    )
    public final void testLoad() throws IOException {
        InputStream is = new ByteArrayInputStream(writeProperties());
        MyProvider myProvider = new MyProvider("name", 1, "info");
        myProvider.load(is);
        assertEquals("tests.security", myProvider.get("test.pkg"));
        assertEquals("Unit Tests", myProvider.get("test.proj"));
        assertNull(myProvider.get("#commented.entry"));

        assertEquals("info", myProvider.get("Provider.id info"));
        String className = myProvider.getClass().toString();
        assertEquals(
                className.substring("class ".length(), className.length()),
                myProvider.get("Provider.id className"));
        assertEquals("1.0", myProvider.get("Provider.id version"));

        try {
            myProvider.load(null);
            fail("NullPointerException expected");
        } catch (NullPointerException e) {
            // expected
        }
    }
    
    @TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "",
            method = "load",
            args = {java.io.InputStream.class}
        )    
    public final void testLoad2() {
        class TestInputStream extends InputStream {
            @Override
            public int read() throws IOException {
                throw new IOException();
            }
        }
        
        MyProvider p = new MyProvider();
        try {
            p.load(new TestInputStream());
            fail("expected IOException");
        } catch (IOException e) {
            // ok
        }
        
    }

    protected byte[] writeProperties() {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(bout);
        ps.println("#commented.entry=Bogus");
        ps.println("test.pkg=tests.security");
        ps.println("test.proj=Unit Tests");
        ps.close();
        return bout.toByteArray();
    }

    class MyProvider extends Provider {
       // private Set<Provider.Service> services = null;

        MyProvider() {
            super("MyProvider", 1.0, "Provider for testing");
            put("MessageDigest.ASH-1", "SomeClassName");
            put("MessageDigest.abc", "SomeClassName");
            put("Alg.Alias.MessageDigest.ASH1", "ASH-1");
        }

        MyProvider(String name, double version, String info) {
            super(name, version, info);
        }

        public void putService(Provider.Service s) {
            super.putService(s);
        }

        public void removeService(Provider.Service s) {
            super.removeService(s);
        }

        public int getNumServices() {
            return getServices().size();
        }
    }
    
    static class TestSecurityManager extends SecurityManager {
        boolean called = false;
        private final String permissionName;
        
        public TestSecurityManager(String permissionName) {
            this.permissionName = permissionName;
        }
        
        @Override
        public void checkPermission(Permission permission) {
            if (permission instanceof SecurityPermission) {
                if (permissionName.equals(permission.getName())) {
                    called = true;
                }
            }
        }
    }
}
