/*
 * Autopsy Forensic Browser
 *
 * Copyright 2014 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 *
 * 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 org.sleuthkit.autopsy.ingest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.TreeMap;
import java.util.logging.Level;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.examples.SampleExecutableIngestModuleFactory;
import org.sleuthkit.autopsy.examples.SampleIngestModuleFactory;
import org.sleuthkit.autopsy.modules.android.AndroidModuleFactory;
import org.sleuthkit.autopsy.modules.e01verify.E01VerifierModuleFactory;
import org.sleuthkit.autopsy.modules.exif.ExifParserModuleFactory;
import org.sleuthkit.autopsy.modules.fileextmismatch.FileExtMismatchDetectorModuleFactory;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory;
import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory;
import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory;
import org.sleuthkit.autopsy.modules.sevenzip.ArchiveFileExtractorModuleFactory;
import org.sleuthkit.autopsy.python.JythonModuleLoader;

/**
 * Discovers and instantiates ingest module factories.
 */
final class IngestModuleFactoryLoader {

    private static final Logger logger = Logger.getLogger(IngestModuleFactoryLoader.class.getName());
    private static final String SAMPLE_MODULE_FACTORY_CLASS_NAME = SampleIngestModuleFactory.class.getCanonicalName();
    private static final String SAMPLE_EXECUTABLE_MODULE_FACTORY_CLASS_NAME = SampleExecutableIngestModuleFactory.class.getCanonicalName();
    private static final ArrayList<String> coreModuleOrdering = new ArrayList<String>() {
        {
            // The ordering of the core ingest module factories implemented
            // using Java is hard-coded. 
            add("org.sleuthkit.autopsy.recentactivity.RecentActivityExtracterModuleFactory"); //NON-NLS
            add(HashLookupModuleFactory.class.getCanonicalName());
            add(FileTypeIdModuleFactory.class.getCanonicalName());
            add(ArchiveFileExtractorModuleFactory.class.getCanonicalName());
            add(ExifParserModuleFactory.class.getCanonicalName());
            add("org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleFactory"); //NON-NLS
            add("org.sleuthkit.autopsy.thunderbirdparser.EmailParserModuleFactory"); //NON-NLS
            add(FileExtMismatchDetectorModuleFactory.class.getCanonicalName());
            add(E01VerifierModuleFactory.class.getCanonicalName());
            add(AndroidModuleFactory.class.getCanonicalName());
            add(InterestingItemsIngestModuleFactory.class.getCanonicalName());
            add(PhotoRecCarverIngestModuleFactory.class.getCanonicalName());
        }
    };

    /**
     * Gets the currently available set of ingest module factories. The
     * factories are not cached between calls since NetBeans modules with
     * classes annotated as IngestModuleFactory service providers and/or Python
     * scripts defining classes derived from IngestModuleFactory may be added or
     * removed between invocations.
     *
     * @return A list of objects that implement the IngestModuleFactory
     * interface.
     */
    static List<IngestModuleFactory> getIngestModuleFactories() {
        // A hash set of display names and a hash map of class names to 
        // discovered factories are used to de-duplicate and order the 
        // factories.
        HashSet<String> moduleDisplayNames = new HashSet<>();
        HashMap<String, IngestModuleFactory> javaFactoriesByClass = new HashMap<>();

        // Discover the ingest module factories implemented using Java with a
        // service provider annotation for the IngestModuleFactory interface.
        for (IngestModuleFactory factory : Lookup.getDefault().lookupAll(IngestModuleFactory.class)) {
            IngestModuleFactoryLoader.addFactory(factory, moduleDisplayNames, javaFactoriesByClass);
        }

        // Discover the ingest module factories implemented using Java with a
        // service provider annotation for the IngestModuleFactoryAdapter 
        // abstract base class.
        for (IngestModuleFactory factory : Lookup.getDefault().lookupAll(IngestModuleFactoryAdapter.class)) {
            if (!javaFactoriesByClass.containsValue(factory)) {
                IngestModuleFactoryLoader.addFactory(factory, moduleDisplayNames, javaFactoriesByClass);
            }
        }

        // Add the core ingest module factories in the desired order, removing
        // the core factories from the map so that the map will only contain 
        // non-core modules after this loop.
        List<IngestModuleFactory> factories = new ArrayList<>();
        for (String className : coreModuleOrdering) {
            IngestModuleFactory coreFactory = javaFactoriesByClass.remove(className);
            if (coreFactory != null) {
                factories.add(coreFactory);
            } else {
                logger.log(Level.SEVERE, "Core factory {0} not loaded", coreFactory);
            }
        }

        // Add any remaining non-core factories discovered. Order with an 
        // alphabetical sort by module display name.
        TreeMap<String, IngestModuleFactory> javaFactoriesSortedByName = new TreeMap<>(); 
        for (IngestModuleFactory factory : javaFactoriesByClass.values()) {
            javaFactoriesSortedByName.put(factory.getModuleDisplayName(), factory);
        }                
        factories.addAll(javaFactoriesSortedByName.values());

        // Add any ingest module factories implemented using Jython. Order is 
        // not guaranteed! 
        for (IngestModuleFactory factory : JythonModuleLoader.getIngestModuleFactories()) {
            if (!moduleDisplayNames.contains(factory.getModuleDisplayName())) {
                moduleDisplayNames.add(factory.getModuleDisplayName());
                factories.add(factory);
                logger.log(Level.INFO, "Found ingest module factory: name = {0}, version = {1}", new Object[]{factory.getModuleDisplayName(), factory.getModuleVersionNumber()}); //NON-NLS
            } else {
                logger.log(Level.SEVERE, "Found duplicate ingest module display name (name = {0})", factory.getModuleDisplayName()); //NON-NLS
                DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
                        NbBundle.getMessage(IngestModuleFactoryLoader.class, "IngestModuleFactoryLoader.errorMessages.duplicateDisplayName", factory.getModuleDisplayName()),
                        NotifyDescriptor.ERROR_MESSAGE));
            }
        }

        return factories;
    }

    private static void addFactory(IngestModuleFactory factory, HashSet<String> moduleDisplayNames, HashMap<String, IngestModuleFactory> javaFactoriesByClass) {
        // Ignore the sample ingest module factories implemented in Java.        
        String className = factory.getClass().getCanonicalName();
        if (className.equals(IngestModuleFactoryLoader.SAMPLE_MODULE_FACTORY_CLASS_NAME)
                || className.equals(IngestModuleFactoryLoader.SAMPLE_EXECUTABLE_MODULE_FACTORY_CLASS_NAME)) {
            return;
        }

        if (!moduleDisplayNames.contains(factory.getModuleDisplayName())) {
            moduleDisplayNames.add(factory.getModuleDisplayName());
            javaFactoriesByClass.put(factory.getClass().getCanonicalName(), factory);
            logger.log(Level.INFO, "Found ingest module factory: name = {0}, version = {1}", new Object[]{factory.getModuleDisplayName(), factory.getModuleVersionNumber()}); //NON-NLS
        } else {
            logger.log(Level.SEVERE, "Found duplicate ingest module display name (name = {0})", factory.getModuleDisplayName()); //NON-NLS
            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
                    NbBundle.getMessage(IngestModuleFactoryLoader.class, "IngestModuleFactoryLoader.errorMessages.duplicateDisplayName", factory.getModuleDisplayName()),
                    NotifyDescriptor.ERROR_MESSAGE));
        }
    }

}

@@ -5883,26 +5883,24 @@
 aded%22, c
-oreFactory
+lassName
 );%0A     

Fix error message bug in IngestFactoryModuleLoader class