/*
 * Copyright 2015 Decebal Suiu
 *
 * Licensed 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 ro.fortsoft.pf4j;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Decebal Suiu
 */
public abstract class AbstractExtensionFinder implements ExtensionFinder, PluginStateListener {

    private static final Logger log = LoggerFactory.getLogger(AbstractExtensionFinder.class);

    protected PluginManager pluginManager;
    protected volatile Map<String, Set<String>> entries; // cache by pluginId

    public AbstractExtensionFinder(PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

    public abstract Map<String, Set<String>> readPluginsStorages();

    public abstract Map<String, Set<String>> readClasspathStorages();

    @Override
    @SuppressWarnings("unchecked")
	public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
		log.debug("Finding extensions for extension point '{}'", type.getName());
        Map<String, Set<String>> entries = getEntries();

        List<ExtensionWrapper<T>> result = new ArrayList<>();
        for (Map.Entry<String, Set<String>> entry : entries.entrySet()) {
            String pluginId = entry.getKey();

            if (pluginId != null) {
                PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
                if (PluginState.STARTED != pluginWrapper.getPluginState()) {
                    continue;
                }
            }

            ClassLoader classLoader = (pluginId != null) ? pluginManager.getPluginClassLoader(pluginId) : getClass().getClassLoader();

            for (String className : entry.getValue()) {
                try {
                    log.debug("Loading class '{}' using class loader '{}'", className, classLoader);
                    Class<?> extensionClass = classLoader.loadClass(className);

                    log.debug("Checking extension type '{}'", className);
                    if (type.isAssignableFrom(extensionClass)) {
                        ExtensionDescriptor descriptor = new ExtensionDescriptor();
                        int ordinal = 0;
                        if (extensionClass.isAnnotationPresent(Extension.class)) {
                            ordinal = extensionClass.getAnnotation(Extension.class).ordinal();
                        }
                        descriptor.setOrdinal(ordinal);
                        descriptor.setExtensionClass(extensionClass);

                        ExtensionWrapper extensionWrapper = new ExtensionWrapper<>(descriptor);
                        extensionWrapper.setExtensionFactory(pluginManager.getExtensionFactory());
                        result.add(extensionWrapper);
                        log.debug("Added extension '{}' with ordinal {}", className, ordinal);
                    } else if (log.isTraceEnabled()) { // an alternative may be the use of a system property
                        log.trace("'{}' is not an extension for extension point '{}'", className, type.getName());
                        ClassLoader extensionClassLoader = extensionClass.getClassLoader();
                        ClassLoader typeClassLoader = type.getClassLoader();
                        if (!extensionClassLoader.equals(typeClassLoader)) {
                            log.trace("Different class loaders: '{}' and '{}'", extensionClassLoader, typeClassLoader);
                        }
                    }
                } catch (ClassNotFoundException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }

        if (entries.isEmpty()) {
        	log.debug("No extensions found for extension point '{}'", type.getName());
        } else {
        	log.debug("Found {} extensions for extension point '{}'", result.size(), type.getName());
        }

        // sort by "ordinal" property
        Collections.sort(result);

		return result;
	}

    @Override
    public Set<String> findClassNames(String pluginId) {
        return getEntries().get(pluginId);
    }

    @Override
	public void pluginStateChanged(PluginStateEvent event) {
        // TODO optimize (do only for some transitions)
        // clear cache
        entries = null;
    }

    private Map<String, Set<String>> readStorages() {
        Map<String, Set<String>> result = new LinkedHashMap<>();

        result.putAll(readClasspathStorages());
        result.putAll(readPluginsStorages());

        return result;
    }

    private Map<String, Set<String>> getEntries() {
        if (entries == null) {
            entries = readStorages();
        }

        return entries;
    }

}

@@ -1810,24 +1810,111 @@
 trySet()) %7B%0A
+            if (entry.getValue().isEmpty()) %7B%0A                continue;%0A            %7D%0A%0A
             
@@ -1943,24 +1943,24 @@
 y.getKey();%0A
-
 %0A           
@@ -2337,24 +2337,767 @@
 sLoader();%0A%0A
+            // performs some check%0A            if (log.isTraceEnabled()) %7B // an alternative may be the use of a system property%0A                ClassLoader typeClassLoader = type.getClassLoader();%0A                if (!classLoader.equals(typeClassLoader))%0A                %7B%0A                    // in this scenario the method 'isAssignableFrom' returns only FALSE%0A                    // see http://www.coderanch.com/t/557846/java/java/FWIW-FYI-isAssignableFrom-isInstance-differing%0A                    log.error(%22Different class loaders: '%7B%7D' and '%7B%7D'%22, classLoader, typeClassLoader);%0A%0A                    // below line is commented because I wish to see the next log.trace line%0A//                    continue;%0A                %7D%0A            %7D%0A%0A
             
@@ -4314,89 +4314,9 @@
 lse 
-if (log.isTraceEnabled()) %7B // an alternative may be the use of a system property
+%7B
 %0A   
@@ -4431,400 +4431,8 @@
 ));%0A
-                        ClassLoader extensionClassLoader = extensionClass.getClassLoader();%0A                        ClassLoader typeClassLoader = type.getClassLoader();%0A                        if (!extensionClassLoader.equals(typeClassLoader)) %7B%0A                            log.trace(%22Different class loaders: '%7B%7D' and '%7B%7D'%22, extensionClassLoader, typeClassLoader);%0A                        %7D%0A
     

Improve debugging for no extensions found