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

/**
 * GROOVY-2875: MetaClassRegistryImpl constantMetaClasses map is leaking resources
 * GROOVY-4481: the listener and iterator mechanism over the MetaClassRegistry wasn't working.
 */
class MetaClassRegistryTest extends GroovyTestCase {

    def registry = GroovySystem.metaClassRegistry
    static initSize
    static {
        try {
            Class.forName("org.codehaus.groovy.vmplugin.v7.IndyInterface", true, MetaClassRegistryTest.classLoader)
        } catch (e) {
        }
        initSize = GroovySystem.metaClassRegistry.metaClassRegistryChangeEventListeners.size()
    }

    void testListenerAdditionAndRemoval() {
        def called = null
        def listener = { event -> called = event } as MetaClassRegistryChangeEventListener
        registry.addMetaClassRegistryChangeEventListener listener

        Integer.metaClass.foo = { -> }
        assert 1.foo() == null
        assert called != null

        def listeners = registry.metaClassRegistryChangeEventListeners
        assert listeners.size() == initSize + 1
        registry.removeMetaClassRegistryChangeEventListener(listener)
        assert registry.metaClassRegistryChangeEventListeners.size() == initSize

        def oldCalled = called;
        Integer.metaClass = null

        Integer.metaClass.bar = {}
        assert 1.bar() == null
        shouldFail(MissingMethodException) {
            1.foo()
        }
        assert called == oldCalled

        Integer.metaClass = null
        shouldFail(MissingMethodException) {
            1.bar()
        }
    }

    void testDefaultListenerRemoval() {
        assert registry.metaClassRegistryChangeEventListeners.size() == initSize
        registry.removeMetaClassRegistryChangeEventListener(registry.metaClassRegistryChangeEventListeners[0])
        assert registry.metaClassRegistryChangeEventListeners.size() == initSize
    }

    void testIteratorIteration() {
        // at the start the iteration might show elements, even if
        // they are no longer in use. After they are added to the list,
        // they can not be collected for now.
        def metaClasses = []
        registry.each { metaClasses << it }

        // we add one more constant meta class and then count them to
        // see if the number fits
        Integer.metaClass.foo = {}

        def count = 0;
        registry.each { count++ }
        assert count == 1 + metaClasses.size()

        // we remove the class again, but it might still show up
        // in the list.. so we don't test that
        Integer.metaClass = null
    }

    void testIteratorRemove() {
        Integer.metaClass.foo { -> 1 }
        assert 1.foo() == 1
        for (def it = registry.iterator(); it.hasNext();) {
            it.remove()
        }
        shouldFail(MissingMethodException) {
            1.foo()
        }
    }

    void testAddingAnEventListenerAndChangingAMetaClassWithAnEMC() {
        def events = []
        def listener = { MetaClassRegistryChangeEvent event ->
            events << event
        } as MetaClassRegistryChangeEventListener

        GroovySystem.metaClassRegistry.addMetaClassRegistryChangeEventListener listener
        String.metaClass.foo = { -> "foo" }

        assert "bar".foo() == "foo"
        assert events.size() == 1
        assert events[0].classToUpdate == String
        assert events[0].oldMetaClass != events[0].newMetaClass
        assert events[0].instance == null

        MetaClass reference = events[0].newMetaClass
        String.metaClass = null

        assert events.size() == 2
        assert events[1].classToUpdate == String
        assert events[1].oldMetaClass == reference
        assert events[1].instance == null

        // now test per instance MC change
        def str = 'foo'
        str.metaClass.foo = { 'bar' }
        assert str.foo() == 'bar'
        assert events.size() == 3
        assert events[2].classToUpdate == String
        assert events[2].instance == str

        GroovySystem.metaClassRegistry.removeMetaClassRegistryChangeEventListener listener
        String.metaClass = null
    }

    void testAddingAnEventListenerAndChangingAMetaClassWithANormalMetaClass() {
        def events = []
        def listener = { MetaClassRegistryChangeEvent event ->
            events << event
        } as MetaClassRegistryChangeEventListener

        GroovySystem.metaClassRegistry.addMetaClassRegistryChangeEventListener listener
        def mc = new MetaClassImpl(Double)
        mc.initialize()
        Double.metaClass = mc

        assert events.size() == 1
        assert events[0].classToUpdate == Double

        GroovySystem.metaClassRegistry.removeMetaClassRegistryChangeEventListener listener
        Double.metaClass = null
    }
}
