/*
 * 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.jackrabbit.vault.packaging.integration;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import javax.jcr.RepositoryException;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
import org.apache.jackrabbit.vault.fs.io.ImportOptions;
import org.apache.jackrabbit.vault.packaging.Dependency;
import org.apache.jackrabbit.vault.packaging.DependencyException;
import org.apache.jackrabbit.vault.packaging.NoSuchPackageException;
import org.apache.jackrabbit.vault.packaging.PackageException;
import org.apache.jackrabbit.vault.packaging.PackageExistsException;
import org.apache.jackrabbit.vault.packaging.PackageId;
import org.apache.jackrabbit.vault.packaging.registry.DependencyReport;
import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlan;
import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlanBuilder;
import org.apache.jackrabbit.vault.packaging.registry.PackageTask;
import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage;
import org.apache.jackrabbit.vault.packaging.registry.PackageTask.Type;
import org.apache.jackrabbit.vault.packaging.registry.impl.FSInstallState;
import org.apache.jackrabbit.vault.packaging.registry.impl.FSPackageRegistry;
import org.apache.jackrabbit.vault.packaging.registry.impl.FSPackageStatus;
import org.apache.jackrabbit.vault.packaging.registry.impl.InstallationScope;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * Test the Package registry interface
 */
public class TestFSPackageRegistry extends IntegrationTestBase {

    private static final File DIR_REGISTRY_HOME = new File("target/registry");

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

    public static final String[] APPLICATION_PATHS = {
            "/libs",
            "/libs/foo"
    };
    
    public static final String[] CONTENT_PATHS = {
            "/tmp",
            "/tmp/foo"
    };
    
    private FSPackageRegistry registry;
    private File registryHome;

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
        if (DIR_REGISTRY_HOME.exists()) {
            FileUtils.cleanDirectory(DIR_REGISTRY_HOME);
        } else {
            DIR_REGISTRY_HOME.mkdir();
        }
        
        getFreshRegistry();
    }
    
    /**
     * checks is a non existent package is really not there
     */
    @Test
    public void testOpenNonExistentPackage() throws IOException, PackageException {
        assertFalse("package must not exist", registry.contains(TMP_PACKAGE_ID));
        assertNull("package must not exist", registry.open(TMP_PACKAGE_ID));
    }

    /**
     * registers a package via stream
     */
    @Test
    public void testRegisterStream() throws IOException, PackageException {
        PackageId id = registry.register(getStream("testpackages/tmp.zip"), false);
        assertEquals("package id", TMP_PACKAGE_ID, id);

        try (RegisteredPackage pkg = registry.open(id)) {
            assertEquals("package id of registered is correct", TMP_PACKAGE_ID, pkg.getId());
            assertFalse("Package is not installed", pkg.isInstalled());
        }
    }

    /**
     * registers a package twice via stream (replace = false)
     */
    @Test
    public void testRegisterStreamTwiceFails() throws IOException, PackageException {
        PackageId id = registry.register(getStream("testpackages/tmp.zip"), false);
        assertEquals("package id", TMP_PACKAGE_ID, id);

        try {
            registry.register(getStream("testpackages/tmp.zip"), false);
            fail("registering the package twice should fail");
        } catch (PackageExistsException e) {
            // expected
            assertEquals("colliding pid must be correct", id, e.getId());
        }
    }

    /**
     * registers a package twice via stream (replace = true)
     */
    @Test
    public void testRegisterStreamTwiceSucceeds() throws IOException, PackageException {
        PackageId id = registry.register(getStream("testpackages/tmp.zip"), false);
        assertEquals("package id", TMP_PACKAGE_ID, id);
        registry.register(getStream("testpackages/tmp.zip"), true);
    }

    /**
     * registers a package twice via a file
     */
    @Test
    public void testRegisterFileTwiceFails() throws IOException, PackageException {
        File file = getTempFile("testpackages/tmp.zip");
        PackageId id = registry.register(file, false);
        assertEquals("package id", TMP_PACKAGE_ID, id);
        assertTrue("file should still exist", file.exists());

        try (RegisteredPackage pkg = registry.open(id)) {
            assertEquals("package id of registered is correct", TMP_PACKAGE_ID, pkg.getId());
            assertFalse("Package is not installed", pkg.isInstalled());
        }

        try {
            registry.register(file, false);
            fail("registering the package twice should fail");
        } catch (PackageExistsException e) {
            // expected
            assertEquals("colliding pid must be correct", id, e.getId());
        } finally {
            file.delete();
        }
    }

    /**
     * registers a package twice via a temp file file
     */
    @Test
    public void testRegisterTempFileTwiceFails() throws IOException, PackageException {
        File file = getTempFile("testpackages/tmp.zip");
        PackageId id = registry.register(file, false);
        assertEquals("package id", TMP_PACKAGE_ID, id);

        try (RegisteredPackage pkg = registry.open(id)) {
            assertEquals("package id of registered is correct", TMP_PACKAGE_ID, pkg.getId());
            assertFalse("Package is not installed", pkg.isInstalled());
        }

        file = getTempFile("testpackages/tmp.zip");
        try {
            registry.register(file, false);
            fail("registering the package twice should fail");
        } catch (PackageExistsException e) {
            // expected
            assertEquals("colliding pid must be correct", id, e.getId());
        }
    }

    /**
     * registers a package twice via file (replace = true)
     */
    @Test
    public void testRegisterFileTwiceSucceeds() throws IOException, PackageException {
        File file = getTempFile("testpackages/tmp.zip");
        PackageId id = registry.register(file, false);
        assertEquals("package id", TMP_PACKAGE_ID, id);
        assertTrue("file should still exist", file.exists());
        registry.register(file, true);
        file.delete();
    }
    
    /**
     * registers a file as external package twice (replace = false)
     */
    @Test
    public void testRegisterExternalFileTwiceFails() throws IOException, PackageException {
        File file = getTempFile("testpackages/tmp.zip");
        PackageId id = registry.registerExternal(file, false);
        assertEquals("package id", TMP_PACKAGE_ID, id);

        try (RegisteredPackage pkg = registry.open(id)) {
            assertEquals("package id of registered is correct", TMP_PACKAGE_ID, pkg.getId());
            assertFalse("Package is not installed", pkg.isInstalled());
        }

        file = getTempFile("testpackages/tmp.zip");
        try {
            registry.registerExternal(file, false);
            fail("registering the package twice should fail");
        } catch (PackageExistsException e) {
            // expected
            assertEquals("colliding pid must be correct", id, e.getId());
        }
    }
    
    /**
     * registers a file as external package twice with 
     */
    @Test
    public void testRegisterExternalFileTwiceFailsLoadedRegistry() throws IOException, PackageException {
        File file = getTempFile("testpackages/tmp.zip");
        PackageId id = registry.registerExternal(file, false);
        assertEquals("package id", TMP_PACKAGE_ID, id);

        try (RegisteredPackage pkg = registry.open(id)) {
            assertEquals("package id of registered is correct", TMP_PACKAGE_ID, pkg.getId());
            assertFalse("Package is not installed", pkg.isInstalled());
        }
        
        // loading registry again to force loading of metadata from files
        registry = new FSPackageRegistry(registryHome);
        
        try {
            registry.registerExternal(file, false);
            fail("registering the package twice should fail");
        } catch (PackageExistsException e) {
            // expected
            assertEquals("colliding pid must be correct", id, e.getId());
        }
    }


    /**
     * registers a file as external package twice (replace = false)
     */
    @Test
    public void testRegisterExternalFileTwiceSucceeds() throws IOException, PackageException {
        File file = getTempFile("testpackages/tmp.zip");
        PackageId id = registry.registerExternal(file, false);
        assertEquals("package id", TMP_PACKAGE_ID, id);
        assertTrue("file should still exist", file.exists());
        registry.registerExternal(file, true);
        file.delete();
    }
    
    /**
     * registers a file as external package with subpackages (replace = true)
     */
    @Test
    public void testRegisterExternalWithSubPackages() throws IOException, PackageException {
        File file = getTempFile("testpackages/subtest.zip");
        registry.registerExternal(file, false);

        assertTrue(registry.contains(PACKAGE_ID_SUB_A));
        assertTrue(registry.contains(PACKAGE_ID_SUB_B));
    }
    
    /**
     * installs a file as external package with subpackages 
     * Subpackages are only installed if either explicitly added to executionPlan or another package depends on them.
     */
    @Test
    public void testInstallExternalWithSubPackages() throws IOException, PackageException {
        File file = getTempFile("testpackages/subtest.zip");
        PackageId parentPkg = registry.registerExternal(file, false);
        
        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });
        builder.addTask().with(parentPkg).with(Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());
        
        assertFalse(registry.open(PACKAGE_ID_SUB_A).isInstalled());
        assertFalse(registry.open(PACKAGE_ID_SUB_B).isInstalled());
    }
    
    


    @SuppressWarnings("deprecation")
    @Test
    public void testInstallExternalUnScoped() throws IOException, PackageException, RepositoryException, org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException {
        File file = getTempFile("testpackages/mixed_package.zip");
        
        cleanPaths(APPLICATION_PATHS);
        cleanPaths(CONTENT_PATHS);
        getFreshRegistry();

        PackageId pkg = registry.registerExternal(file, false);
        
        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        Collector listener = new Collector();
        builder.with(listener);
        builder.addTask().with(pkg).with(Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());
        checkFiltered(APPLICATION_PATHS, new String[] {}, listener.paths);
        checkFiltered(CONTENT_PATHS, new String[] {}, listener.paths);
    }

    @SuppressWarnings("deprecation")
    @Test
    public void testInstallExternalContentScoped() throws IOException, PackageException, RepositoryException, org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException {
        File file = getTempFile("testpackages/mixed_package.zip");

        cleanPaths(APPLICATION_PATHS);
        cleanPaths(CONTENT_PATHS);
        getFreshRegistry(InstallationScope.CONTENT_SCOPED);
        
        PackageId pkg = registry.registerExternal(file, false);
        
        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        Collector listener = new Collector();
        builder.with(listener);
        builder.addTask().with(pkg).with(Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());
        checkFiltered(CONTENT_PATHS, APPLICATION_PATHS, listener.paths);
    }

    @SuppressWarnings("deprecation")
    private void cleanPaths(String[] paths) throws IOException, RepositoryException, org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException  {
        for (String path : paths) {
            clean(path);
        }
    }

    @SuppressWarnings("deprecation")
    @Test
    public void testInstallExternalApplicationScoped() throws IOException, PackageException, RepositoryException, org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException {
        File file = getTempFile("testpackages/mixed_package.zip");
        
        cleanPaths(APPLICATION_PATHS);
        cleanPaths(CONTENT_PATHS);
        getFreshRegistry(InstallationScope.APPLICATION_SCOPED);
        
        PackageId pkg = registry.registerExternal(file, false);
        
        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        Collector listener = new Collector();
        builder.with(listener);
        builder.addTask().with(pkg).with(Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());
        checkFiltered(APPLICATION_PATHS, CONTENT_PATHS, listener.paths);
        
    }
    

    /**
     * test if package removal works
     */
    @Test
    public void testRemovePackage() throws IOException, PackageException {
        PackageId id = registry.register(getStream("testpackages/tmp.zip"), false);
        assertEquals("package id", TMP_PACKAGE_ID, id);

        registry.remove(id);
        assertFalse("package must not exist", registry.contains(TMP_PACKAGE_ID));
        assertNull("package must not exist", registry.open(TMP_PACKAGE_ID));
    }

    /**
     * test packages set
     */
    @Test
    public void testPackages() throws IOException, PackageException {
        assertTrue("initially the packages set is empty", registry.packages().isEmpty());
        registry.register(getStream("testpackages/tmp.zip"), false);
        assertEquals("packages contains 1 element", 1, registry.packages().size());
        assertTrue("contains new package", registry.packages().contains(TMP_PACKAGE_ID));
    }

    /**
     * test if remove non existing should fail
     */
    @Test
    public void testRemoveNonExistingPackage() throws IOException, PackageException {
        try {
            registry.remove(TMP_PACKAGE_ID);
            fail("remove non existing should fail");
        } catch (NoSuchPackageException e) {
            assertEquals("exception should contain correct package id", TMP_PACKAGE_ID, e.getId());
        }
    }

    /**
     * test if analyze dependencies fails for non existing package
     */
    @Test
    public void testAnalyzeDependenciesFailsForNonExisting() throws IOException {
        try {
            registry.analyzeDependencies(TMP_PACKAGE_ID, false);
            fail("usage report should fail for non existing package.");
        } catch (NoSuchPackageException e) {
            assertEquals("exception should contain correct package id", TMP_PACKAGE_ID, e.getId());
        }
    }

    /**
     * test if analyze dependencies is correct for non installed packages
     */
    @Test
    public void testAnalyzeDependencies() throws IOException, PackageException {
        // a depends on b and c
        PackageId idA = registry.register(getStream(TEST_PACKAGE_A_10), false);

        DependencyReport report = registry.analyzeDependencies(idA, false);
        assertEquals("resolved dependencies", "", PackageId.toString(report.getResolvedDependencies()));
        assertEquals("unresolved dependencies", "my_packages:test_b,my_packages:test_c:[1.0,2.0)",
                Dependency.toString(report.getUnresolvedDependencies()));

        // b depends on c
        registry.register(getStream(TEST_PACKAGE_B_10), false);
        report = registry.analyzeDependencies(idA, false);
        assertEquals("resolved dependencies", "my_packages:test_b:1.0",
                PackageId.toString(report.getResolvedDependencies()));
        assertEquals("unresolved dependencies", "my_packages:test_c:[1.0,2.0)",
                Dependency.toString(report.getUnresolvedDependencies()));

        registry.register(getStream(TEST_PACKAGE_C_10), false);
        report = registry.analyzeDependencies(idA, false);
        assertEquals("resolved dependencies", "my_packages:test_b:1.0,my_packages:test_c:1.0",
                PackageId.toString(report.getResolvedDependencies()));
        assertEquals("unresolved dependencies", "", Dependency.toString(report.getUnresolvedDependencies()));

    }

    @Test
    public void testUnsupportedInstallTasks() throws IOException, PackageException, RepositoryException {
        // a depends on b and c
        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);

        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });

        builder.addTask().with(idC).with(PackageTask.Type.INSTALL);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertTrue(plan.hasErrors());
        assertFalse(registry.open(idC).isInstalled());
    }

    @Test
    public void testExecutionPlanInstallation() throws IOException, PackageException, RepositoryException {
        // a depends on b and c
        PackageId idA = registry.register(getStream(TEST_PACKAGE_A_10), false);

        DependencyReport report = registry.analyzeDependencies(idA, false);
        assertEquals("resolved dependencies", "", PackageId.toString(report.getResolvedDependencies()));
        assertEquals("unresolved dependencies", "my_packages:test_b,my_packages:test_c:[1.0,2.0)",
                Dependency.toString(report.getUnresolvedDependencies()));

        // b depends on c
        PackageId idB = registry.register(getStream(TEST_PACKAGE_B_10), false);
        report = registry.analyzeDependencies(idB, false);
        assertEquals("resolved dependencies", "", PackageId.toString(report.getResolvedDependencies()));
        assertEquals("unresolved dependencies", "my_packages:test_c",
                Dependency.toString(report.getUnresolvedDependencies()));

        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });

        builder.addTask().with(idA).with(PackageTask.Type.EXTRACT);
        builder.addTask().with(idB).with(PackageTask.Type.EXTRACT);
        try {
            builder.with(admin).execute();
            fail("registering the package with missing dependencies should fail");
        } catch (DependencyException ex) {
            // expected
        }
        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);
        report = registry.analyzeDependencies(idB, false);
        assertEquals("resolved dependencies", "my_packages:test_c:1.0",
                PackageId.toString(report.getResolvedDependencies()));
        assertEquals("unresolved dependencies", "", Dependency.toString(report.getUnresolvedDependencies()));

        builder.addTask().with(idC).with(PackageTask.Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());

        assertTrue(registry.open(idA).isInstalled());
        assertTrue(registry.open(idB).isInstalled());
        assertTrue(registry.open(idC).isInstalled());
    }
    
    @Test
    public void testExtractSubPackage() throws IOException, PackageException, RepositoryException {
        registry.register(getStream("testpackages/subtest.zip"), false);

        assertTrue(registry.contains(PACKAGE_ID_SUB_A));
        assertTrue(registry.contains(PACKAGE_ID_SUB_B));
    }

    @Test
    public void testUsages() throws Exception {
        PackageId idB = registry.register(getStream(TEST_PACKAGE_B_10), false);
        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);

        assertEquals("usage", "", PackageId.toString(registry.usage(idC)));

        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });

        builder.addTask().with(idB).with(PackageTask.Type.EXTRACT);
        builder.addTask().with(idC).with(PackageTask.Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());

        assertEquals("usage", "my_packages:test_b:1.0", PackageId.toString(registry.usage(idC)));
    }
    
    @Test
    public void testInstalledDependencies() throws Exception {
        PackageId idB = registry.register(getStream(TEST_PACKAGE_B_10), false);
        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);

        assertEquals("usage", "", PackageId.toString(registry.usage(idC)));

        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });

        builder.addTask().with(idC).with(PackageTask.Type.EXTRACT);
        ExecutionPlan plan  = builder.with(admin).execute();
        assertFalse(plan.hasErrors());

        Dependency depB = new Dependency(idB);
        Dependency depC = new Dependency(idC);
        assertNull("Dependency B should not resolve", registry.resolve(depB, true));
        assertEquals("Dependency C should resovle to package C", idC, registry.resolve(depC, true));
    }
    
    
    @Test
    public void testInstallTime() throws Exception {

        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);

        assertNull(registry.open(idC).getInstallationTime());
        
        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });

        builder.addTask().with(idC).with(PackageTask.Type.EXTRACT);
        Calendar before = Calendar.getInstance();
        ExecutionPlan plan = builder.with(admin).execute();
        Calendar after = Calendar.getInstance();
        assertFalse(plan.hasErrors());

        assertTrue("Installation time for idC too late", registry.open(idC).getInstallationTime().compareTo(after) <= 0);
        assertTrue("Installation time for idC too early", registry.open(idC).getInstallationTime().compareTo(before) >= 0);

    }
    
    @Test
    public void testUnsupportedUninstall() throws Exception {
        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);

        assertNull(registry.open(idC).getInstallationTime());
        
        ExecutionPlanBuilder builder = registry.createExecutionPlan();
        builder.with(new ProgressTrackerListener() {
            public void onMessage(Mode mode, String action, String path) {
                log.info("{} {}", action, path);
            }

            public void onError(Mode mode, String path, Exception e) {
                log.info("E {} {}", path, e.toString());
            }
        });

        builder.addTask().with(idC).with(PackageTask.Type.EXTRACT);
        ExecutionPlan plan = builder.with(admin).execute();
        assertFalse(plan.hasErrors());

        try{
            registry.uninstallPackage(admin, registry.open(idC), new ImportOptions());
            fail("uninstall attempt should fail.");
        } catch (PackageException ex) {
            //expected
        }
    }
    
    @Test
    public void testNonMetaXmlFile() throws Exception {
        PackageId idC = registry.register(getStream(TEST_PACKAGE_C_10), false);

        assertEquals(idC, registry.getInstallState(idC).getPackageId());
        assertEquals(FSPackageStatus.REGISTERED, registry.getInstallState(idC).getStatus());
        
        Field stateCache = registry.getClass().getDeclaredField("stateCache");
        stateCache.setAccessible(true);
        stateCache.set(registry, new HashMap<String, FSInstallState>()  );
        Method getPackageMetaDataFile = registry.getClass().getDeclaredMethod("getPackageMetaDataFile", new Class<?>[]{idC.getClass()});
        getPackageMetaDataFile.setAccessible(true);
        Method loadPackageCache = registry.getClass().getDeclaredMethod("loadPackageCache");
        loadPackageCache.setAccessible(true);
        File metaFile = (File)getPackageMetaDataFile.invoke(registry, idC);
        FileUtils.copyToFile(getStream("repository.xml"), metaFile);
        loadPackageCache.invoke(registry);
        assertEquals(FSPackageStatus.NOTREGISTERED, registry.getInstallState(idC).getStatus());
    }
    
    private void getFreshRegistry(InstallationScope... scope) throws IOException {
        if (this.registryHome != null && this.registryHome.exists()) {
            this.registryHome.delete();
        }
        this.registryHome = new File(DIR_REGISTRY_HOME, UUID.randomUUID().toString());
        this.registryHome.mkdir();
        if (scope.length > 0) {
            this.registry = new FSPackageRegistry(registryHome, scope[0]);
        } else {
            this.registry = new FSPackageRegistry(registryHome);
        }
    }
    
    private static class Collector implements ProgressTrackerListener {
        private final List<String> paths = new LinkedList<String>();

        public void onMessage(Mode mode, String action, String path) {
            paths.add(path);
        }

        public void onError(Mode mode, String path, Exception e) {
        }
    }
    
    public static void checkFiltered(String[] containing, String[] filtered, List<String> result) {
        assertEquals("Results don't contain expected values", Collections.EMPTY_LIST, CollectionUtils.subtract(Arrays.asList(containing), result));
        assertEquals("Results contain unexpected values", Collections.EMPTY_LIST , CollectionUtils.intersection(result, Arrays.asList(filtered)));
    }

}