/*
 * 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.aries.subsystem.itests.defect;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.aries.subsystem.itests.SubsystemTest;
import org.apache.aries.subsystem.itests.util.TestCapability;
import org.apache.aries.subsystem.itests.util.TestRepository;
import org.apache.aries.subsystem.itests.util.TestRepositoryContent;
import org.eclipse.equinox.region.Region;
import org.eclipse.equinox.region.RegionDigraph;
import org.eclipse.equinox.region.RegionDigraph.FilteredRegion;
import org.eclipse.equinox.region.RegionFilter;
import org.junit.Before;
import org.junit.Test;
import org.ops4j.pax.tinybundles.core.InnerClassStrategy;
import org.ops4j.pax.tinybundles.core.TinyBundles;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.framework.hooks.weaving.WovenClass;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.resource.Resource;
import org.osgi.service.repository.Repository;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.SubsystemConstants;

/*
 * https://issues.apache.org/jira/browse/ARIES-1435
 * 
 * Sharing policy updates for dynamic imports, if necessary, should proceed up 
 * the subsystem region tree.
 */
public class Aries1435Test extends SubsystemTest {
	private static final String APPLICATION_A = "application.a.esa";
	private static final String APPLICATION_B = "application.b.esa";
	private static final String APPLICATION_C = "application.c.esa";
	private static final String APPLICATION_D = "application.d.esa";
	private static final String APPLICATION_E = "application.e.esa";
	private static final String BUNDLE_A = "bundle.a.jar";
	private static final String BUNDLE_B = "bundle.b.jar";
	private static final String COMPOSITE_A = "composite.a.esa";
	private static final String COMPOSITE_B = "composite.b.esa";
	private static final String COMPOSITE_C = "composite.c.esa";
	private static final String COMPOSITE_D = "composite.d.esa";
	private static final String COMPOSITE_E = "composite.e.esa";
	private static final String COMPOSITE_F = "composite.f.esa";
	private static final String FEATURE_A = "feature.a.esa";
	private static final String FEATURE_B = "feature.b.esa";
	private static final String FEATURE_C = "feature.c.esa";
	private static final String FEATURE_D = "feature.d.esa";
	
	private static boolean createdTestFiles;
	@Before
	public void createTestFiles() throws Exception {
		if (createdTestFiles)
			return;
		createApplicationB();
		createApplicationA();	
		createCompositeA();
		createCompositeB();
		createApplicationC();
		createFeatureA();
		createFeatureB();
		createApplicationD();
		createCompositeC();
		createFeatureC();
		createCompositeD();
		createFeatureD();
		createApplicationE();
		createCompositeE();
		createCompositeF();
		createdTestFiles = true;
	}
	
	private AtomicBoolean weavingHookCalled;
	@Override
    public void setUp() throws Exception {
        super.setUp();
        try {
            serviceRegistrations.add(
                    bundleContext.registerService(
                            Repository.class, 
                            createTestRepository(), 
                            null));
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        weavingHookCalled = new AtomicBoolean();
    }
	
	@Test
    public void testApplicationWithParentApplication() throws Exception {
		testDynamicImport(APPLICATION_B, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION, APPLICATION_A);
    }
	
	@Test
    public void testApplicationWithParentComposite() throws Exception {
		testDynamicImport(APPLICATION_B, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION, COMPOSITE_A);
    }
	
	@Test
    public void testApplicationWithParentFeature() throws Exception {
		testDynamicImport(APPLICATION_B, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION, FEATURE_A);
    }
	
	@Test
    public void testApplicationWithParentRoot() throws Exception {
		testDynamicImport(APPLICATION_B, SubsystemConstants.SUBSYSTEM_TYPE_APPLICATION, null);
    }
	
	@Test
	public void testChildExportsPackage() throws Exception {
		registerWeavingHook("b");
		Subsystem applicationB = installSubsystemFromFile(APPLICATION_B);
		try {
			Subsystem compositeE = installSubsystemFromFile(applicationB, COMPOSITE_E);
			try {
				startSubsystem(compositeE);
				try {
					testSharingPolicy(applicationB, "b", true);
					testDynamicImport(applicationB, "b.B");
					testSharingPolicy(applicationB, "b", true);
					testSharingPolicy(compositeE, "b", false);
					testSharingPolicy(getRootSubsystem(), "b", false);
				}
				finally {
					stopSubsystemSilently(compositeE);
				}
			}
			finally {
				uninstallSubsystemSilently(compositeE);
			}
		}
		finally {
			uninstallSubsystemSilently(applicationB);
		}
	}
	
	@Test
    public void testCompositeWithParentApplication() throws Exception {
		testDynamicImport(COMPOSITE_B, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE, APPLICATION_C);
    }
	
	@Test
    public void testCompositeWithParentComposite() throws Exception {
		testDynamicImport(COMPOSITE_B, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE, COMPOSITE_C);
    }
	
	@Test
    public void testCompositeWithParentFeature() throws Exception {
		testDynamicImport(COMPOSITE_B, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE, FEATURE_C);
    }
	
	@Test
	public void testDisconnectedEdgeWithParent() throws Exception {
		registerWeavingHook("b");
		Bundle bundleB = getRootSubsystem().getBundleContext().installBundle(
				BUNDLE_B, new ByteArrayInputStream(createBundleBContent()));
		try {
			Subsystem applicationA = installSubsystemFromFile(APPLICATION_A);
			try {
				Subsystem applicationB = getChild(applicationA, APPLICATION_B);
				uninstallSubsystem(applicationB);
				removeConnectionWithParent(applicationA);
				applicationB = installSubsystemFromFile(applicationA, APPLICATION_B);
				try {
					try {
						testDynamicImport(applicationB, "b.B");
						fail("Dynamic import should have failed");
					}
					catch (AssertionError e) {
						// Okay.
					}
					testSharingPolicy(applicationB, "b", true);
					testSharingPolicy(applicationA, "b", false);
					testSharingPolicy(getRootSubsystem(), "b", false);
				}
				finally {
					uninstallSubsystemSilently(applicationB);
				}
			}
			finally {
				uninstallSubsystemSilently(applicationA);
			}
		}
		finally {
			uninstallSilently(bundleB);
		}
	}
	
	@Test
    public void testFeatureWithParentApplication() throws Exception {
		testDynamicImport(FEATURE_B, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE, APPLICATION_D);
    }
	
	@Test
    public void testFeatureWithParentComposite() throws Exception {
		testDynamicImport(FEATURE_B, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE, COMPOSITE_D);
    }
	
	@Test
    public void testFeatureWithParentFeature() throws Exception {
		testDynamicImport(FEATURE_B, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE, FEATURE_D);
    }
	
	@Test
	public void testNoProviders() throws Exception {
		registerWeavingHook("b");
		Subsystem applicationB = installSubsystemFromFile(APPLICATION_B);
		try {
			Bundle bundleA = getConstituentAsBundle(applicationB, BUNDLE_A, null, null);
			bundleA.loadClass("a.A");
			try {
				bundleA.loadClass("b.B");
				fail("Class should not have loaded");
			}
			catch (ClassNotFoundException e) {
				// Okay.
			}
			testSharingPolicy(applicationB, "b", false);
		}
		finally {
			uninstallSubsystemSilently(applicationB);
		}
	}
	
	@Test
	public void testWildcardEverything() throws Exception {
		registerWeavingHook("b", "*");
		Subsystem compositeB = installSubsystemFromFile(COMPOSITE_B);
		try {
			Bundle bundleB = compositeB.getBundleContext().installBundle(
					BUNDLE_B, 
					new ByteArrayInputStream(createBundleBContent()));
			try {
				Subsystem applicationB = installSubsystemFromFile(compositeB, APPLICATION_B);
				Subsystem featureB = installSubsystemFromFile(applicationB, FEATURE_B);
				Subsystem applicationE = installSubsystemFromFile(featureB, APPLICATION_E);
				
				testSharingPolicy(applicationE, "b", false);
				testSharingPolicy(applicationE, "org.osgi.framework.Constants", false);
				testSharingPolicy(applicationB, "b", false);
				testSharingPolicy(applicationB, "org.osgi.framework.Constants", false);
				testSharingPolicy(compositeB, "b", false);
				testSharingPolicy(compositeB, "org.osgi.framework.Constants", false);
				testSharingPolicy(getRootSubsystem(), "b", false);
				testSharingPolicy(getRootSubsystem(), "org.osgi.framework.Constants", false);
				
				testDynamicImport(applicationE, "b.B");
				testDynamicImport(applicationE, "org.osgi.framework.Constants");
				testDynamicImport(featureB, "b.B");
				testDynamicImport(featureB, "org.osgi.framework.Constants");
				testDynamicImport(applicationB, "b.B");
				testDynamicImport(applicationB, "org.osgi.framework.Constants");
				testDynamicImport(compositeB, "b.B");
				testDynamicImport(compositeB, "org.osgi.framework.Constants");
				
				testSharingPolicy(applicationE, "b", true);
				testSharingPolicy(applicationE, "org.osgi.framework.Constants", true);
				testSharingPolicy(applicationB, "b", true);
				testSharingPolicy(applicationB, "org.osgi.framework.Constants", true);
				testSharingPolicy(compositeB, "b", true);
				testSharingPolicy(compositeB, "org.osgi.framework.Constants", true);
				testSharingPolicy(getRootSubsystem(), "b", false);
				testSharingPolicy(getRootSubsystem(), "org.osgi.framework.Constants", false);
				
				uninstallSubsystemSilently(applicationE);
				uninstallSubsystemSilently(featureB);
				uninstallSubsystemSilently(applicationB);
			}
			finally {
				uninstallSilently(bundleB);
			}
		}
		finally {
			uninstallSubsystemSilently(compositeB);
		}
	}
	
	@Test
	public void testWildcardEverythingInPackage() throws Exception {
		registerWeavingHook("b.*");
		Subsystem applicationA = installSubsystemFromFile(APPLICATION_A);
		try {
			Bundle bundleB = applicationA.getBundleContext().installBundle(
					BUNDLE_B, 
					new ByteArrayInputStream(createBundleBContent()));
			try {
				Subsystem applicationB = getChild(applicationA, APPLICATION_B);
				
				testSharingPolicy(applicationB, "b", false);
				testSharingPolicy(applicationB, "b.a", false);
				testSharingPolicy(applicationA, "b", false);
				testSharingPolicy(applicationA, "b.a", false);
				testSharingPolicy(getRootSubsystem(), "b", false);
				testSharingPolicy(getRootSubsystem(), "b.a", false);
				
				testDynamicImport(applicationB, "b.a.A");
				
				testSharingPolicy(applicationB, "b", false);
				testSharingPolicy(applicationB, "b.a", true);
				testSharingPolicy(applicationA, "b", false);
				testSharingPolicy(applicationA, "b.a", true);
				testSharingPolicy(getRootSubsystem(), "b", false);
				testSharingPolicy(getRootSubsystem(), "b.a", false);
			}
			finally {
				uninstallSilently(bundleB);
			}
		}
		finally {
			uninstallSubsystemSilently(applicationA);
		}
	}
	
	@Test
	public void testWovenSubsystemContainsProvider() throws Exception {
		registerWeavingHook("b");
		Subsystem applicationE = installSubsystemFromFile(APPLICATION_E);
		try {
			assertConstituent(applicationE, BUNDLE_B);
			testDynamicImport(applicationE, "b.B");
			testSharingPolicy(applicationE, "b", false);
		}
		finally {
			uninstallSubsystemSilently(applicationE);
		}
	}
	
	@Test
	public void testWovenSubsystemParentContainsProvider() throws Exception {
		registerWeavingHook("b");
		Subsystem compositeE = installSubsystemFromFile(COMPOSITE_E);
		try {
			assertNotConstituent(getRootSubsystem(), BUNDLE_B);
			assertConstituent(compositeE, BUNDLE_B);
			Subsystem applicationB = getChild(compositeE, APPLICATION_B);
			assertNotConstituent(applicationB, BUNDLE_B);
			testDynamicImport(applicationB, "b.B");
			testSharingPolicy(compositeE, "b", false);
			testSharingPolicy(applicationB, "b", true);
		}
		finally {
			uninstallSubsystemSilently(compositeE);
		}
	}
	
	@Test
	public void testWovenSubsystemParentPolicyAllowsProvider() throws Exception {
		registerWeavingHook("b");
		Subsystem root = getRootSubsystem();
		BundleContext context = root.getBundleContext();
		Bundle bundleB = context.installBundle(BUNDLE_B, new ByteArrayInputStream(createBundleBContent()));
		try {
			Subsystem applicationB1 = installSubsystemFromFile(root, new File(APPLICATION_B), APPLICATION_B + "1");
			try {
				Subsystem compositeF = installSubsystemFromFile(applicationB1, COMPOSITE_F);
				try {
					assertPackageFiltersInParentConnection(compositeF, applicationB1, 2, 1);
					Subsystem applicationB2 = installSubsystemFromFile(compositeF, new File(APPLICATION_B), APPLICATION_B + "2");
					try {
						testDynamicImport(applicationB2, "b.B");
						testSharingPolicy(applicationB2, "b", true);
						testSharingPolicy(compositeF, "b", true);
						assertPackageFiltersInParentConnection(compositeF, applicationB1, 2, 1);
						testSharingPolicy(applicationB1, "b", true);
						testSharingPolicy(root, "b", false);
					}
					finally {
						uninstallSubsystemSilently(applicationB2);
					}
				}
				finally {
					uninstallSubsystemSilently(compositeF);
				}
			}
			finally {
				uninstallSubsystemSilently(applicationB1);
			}
		}
		finally {
			uninstallSilently(bundleB);
		}
	}
	
	private void assertPackageFiltersInParentConnection(Subsystem subsystem, Subsystem parent, int expectedEdges, int expectedFilters) {
		Region parentRegion = getRegion(parent);
		Region region = getRegion(subsystem);
		Set<FilteredRegion> edges = region.getEdges();
		assertEquals("Wrong number of edges", expectedEdges, edges.size());
		for (FilteredRegion edge : region.getEdges()) {
			if (!edge.getRegion().equals(parentRegion)) {
				continue;
			}
			RegionFilter filter = edge.getFilter();
			Map<String, Collection<String>> policy = filter.getSharingPolicy();
			Collection<String> packages = policy.get(PackageNamespace.PACKAGE_NAMESPACE);
			assertNotNull("Wrong number of packages", packages);
			assertEquals("Wrong number of packages", expectedFilters, packages.size());
			return;
		}
		fail("No connection to parent found");
	}
	
	private void createApplicationA() throws IOException {
		createApplicationAManifest();
		createSubsystem(APPLICATION_A, APPLICATION_B);
	}
	
	private void createApplicationAManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_A);
		createManifest(APPLICATION_A + ".mf", attributes);
	}
	
	private void createApplicationB() throws IOException {
		createApplicationBManifest();
		createSubsystem(APPLICATION_B);
	}
	
	private void createApplicationBManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_B);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, BUNDLE_A);
		createManifest(APPLICATION_B + ".mf", attributes);
	}
	
	private void createApplicationC() throws IOException {
		createApplicationCManifest();
		createSubsystem(APPLICATION_C, COMPOSITE_B);
	}
	
	private void createApplicationCManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_C);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, COMPOSITE_B + ";type=osgi.subsystem.composite");
		createManifest(APPLICATION_C + ".mf", attributes);
	}
	
	private void createApplicationD() throws IOException {
		createApplicationDManifest();
		createSubsystem(APPLICATION_D, FEATURE_B);
	}
	
	private void createApplicationDManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_D);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, FEATURE_B + ";type=osgi.subsystem.feature");
		createManifest(APPLICATION_D + ".mf", attributes);
	}
	
	private void createApplicationE() throws IOException {
		createApplicationEManifest();
		createSubsystem(APPLICATION_E);
	}
	
	private void createApplicationEManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_E);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, BUNDLE_A + ',' + BUNDLE_B);
		createManifest(APPLICATION_E + ".mf", attributes);
	}
	
	private byte[] createBundleAContent() throws Exception {
        InputStream is = TinyBundles
        		.bundle()
        		.add(getClass().getClassLoader().loadClass("a.A"), InnerClassStrategy.NONE)
				.set(Constants.BUNDLE_SYMBOLICNAME, BUNDLE_A)
				.build(TinyBundles.withBnd());
        return createBundleContent(is);
    }
	
	private Resource createBundleAResource() throws Exception {
        return new TestRepositoryContent.Builder()
        .capability(
                new TestCapability.Builder()
                    .namespace(IdentityNamespace.IDENTITY_NAMESPACE)
                    .attribute(IdentityNamespace.IDENTITY_NAMESPACE, BUNDLE_A)
                    .attribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_BUNDLE)
                    .attribute(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion))
        .content(createBundleAContent())
        .build();
    }
	
	private byte[] createBundleBContent() throws Exception {
        InputStream is = TinyBundles
        		.bundle()
        		.add(getClass().getClassLoader().loadClass("b.B"), InnerClassStrategy.NONE)
        		.add(getClass().getClassLoader().loadClass("b.a.A"), InnerClassStrategy.NONE)
				.set(Constants.BUNDLE_SYMBOLICNAME, BUNDLE_B)
				.set(Constants.EXPORT_PACKAGE, "b,b.a")
				.build(TinyBundles.withBnd());
        return createBundleContent(is);
    }
	
	private byte[] createBundleContent(InputStream is) throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		byte[] bytes = new byte[1024];
        int length;
        while ((length = is.read(bytes)) != -1) {
        	baos.write(bytes, 0, length);
        }
        is.close();
        baos.close();
        return baos.toByteArray();
	}
	
	private Resource createBundleBResource() throws Exception {
        return new TestRepositoryContent.Builder()
        .capability(
                new TestCapability.Builder()
                    .namespace(IdentityNamespace.IDENTITY_NAMESPACE)
                    .attribute(IdentityNamespace.IDENTITY_NAMESPACE, BUNDLE_B)
                    .attribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_BUNDLE)
                    .attribute(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion))
        .capability(
        		new TestCapability.Builder()
        			.namespace(PackageNamespace.PACKAGE_NAMESPACE)
        			.attribute(PackageNamespace.PACKAGE_NAMESPACE, "b")
        			.attribute(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion))
        .capability(
        		new TestCapability.Builder()
        			.namespace(PackageNamespace.PACKAGE_NAMESPACE)
        			.attribute(PackageNamespace.PACKAGE_NAMESPACE, "b.a")
        			.attribute(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion))
        .content(createBundleBContent())
        .build();
    }
	
	private void createCompositeA() throws IOException {
		createCompositeAManifest();
		createSubsystem(COMPOSITE_A, APPLICATION_B);
	}
	
	private void createCompositeAManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_A);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, APPLICATION_B + 
				";type=osgi.subsystem.application;version=\"[0,0]\"");
		createManifest(COMPOSITE_A + ".mf", attributes);
	}
	
	private void createCompositeB() throws IOException {
		createCompositeBManifest();
		createSubsystem(COMPOSITE_B);
	}
	
	private void createCompositeBManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_B);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, BUNDLE_A + ";version=\"[0,0]\"");
		createManifest(COMPOSITE_B + ".mf", attributes);
	}
	
	private void createCompositeC() throws IOException {
		createCompositeCManifest();
		createSubsystem(COMPOSITE_C, COMPOSITE_B);
	}
	
	private void createCompositeCManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_C);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, COMPOSITE_B + ";type=osgi.subsystem.composite;version=\"[0,0]\"");
		createManifest(COMPOSITE_C + ".mf", attributes);
	}
	
	private void createCompositeD() throws IOException {
		createCompositeDManifest();
		createSubsystem(COMPOSITE_D, FEATURE_B);
	}
	
	private void createCompositeDManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_D);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, FEATURE_B + ";type=osgi.subsystem.feature;version=\"[0,0]\"");
		createManifest(COMPOSITE_D + ".mf", attributes);
	}
	
	private void createCompositeE() throws IOException {
		createCompositeEManifest();
		createSubsystem(COMPOSITE_E, APPLICATION_B);
	}
	
	private void createCompositeEManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_E);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, APPLICATION_B + 
				";type=osgi.subsystem.application;version=\"[0,0]\"," +
				BUNDLE_B + ";version=\"[0,0]\"");
		attributes.put(Constants.EXPORT_PACKAGE, "b");
		createManifest(COMPOSITE_E + ".mf", attributes);
	}
	
	private void createCompositeF() throws IOException {
		createCompositeFManifest();
		createSubsystem(COMPOSITE_F);
	}
	
	private void createCompositeFManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_F);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(Constants.IMPORT_PACKAGE, "b;resolution:=optional");
		createManifest(COMPOSITE_F + ".mf", attributes);
	}
	
	private void createFeatureA() throws IOException {
		createFeatureAManifest();
		createSubsystem(FEATURE_A, APPLICATION_B);
	}
	
	private void createFeatureAManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, FEATURE_A);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE);
		createManifest(FEATURE_A + ".mf", attributes);
	}
	
	private void createFeatureB() throws IOException {
		createFeatureBManifest();
		createSubsystem(FEATURE_B);
	}
	
	private void createFeatureBManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, FEATURE_B);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE);
		attributes.put(SubsystemConstants.SUBSYSTEM_CONTENT, BUNDLE_A);
		createManifest(FEATURE_B + ".mf", attributes);
	}
	
	private void createFeatureC() throws IOException {
		createFeatureCManifest();
		createSubsystem(FEATURE_C, COMPOSITE_B);
	}
	
	private void createFeatureCManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, FEATURE_C);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE);
		createManifest(FEATURE_C + ".mf", attributes);
	}
	
	private void createFeatureD() throws IOException {
		createFeatureDManifest();
		createSubsystem(FEATURE_D, FEATURE_B);
	}
	
	private void createFeatureDManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, FEATURE_D);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_FEATURE);
		createManifest(FEATURE_D + ".mf", attributes);
	}
	
	private Repository createTestRepository() throws Exception {
        return new TestRepository.Builder()
        .resource(createBundleAResource())
        .resource(createBundleBResource())
        .build();
    }
	
	private void registerWeavingHook(final String...dynamicImport) {
		serviceRegistrations.add(bundleContext.registerService(
    			WeavingHook.class, 
    			new WeavingHook() {
    				@Override
    				public void weave(WovenClass wovenClass) {
    					Bundle bundle = wovenClass.getBundleWiring().getBundle();
    					String symbolicName = bundle.getSymbolicName();
    					if (BUNDLE_A.equals(symbolicName)) {
    						weavingHookCalled.set(true);
    						List<String> dynamicImports = wovenClass.getDynamicImports();
    						dynamicImports.addAll(Arrays.asList(dynamicImport));
    					}
    				}
    			}, 
    			null));
	}
	
	private void testDynamicImport(String child, String type, String parent) throws Exception {
		testDynamicImport(child, type, parent, "org.osgi.framework");
	}
	
	private void testDynamicImport(String child, String type, String parent, String dynamicImport) throws Exception {
		registerWeavingHook(dynamicImport);
		Subsystem p = installSubsystemFromFile(parent == null ? child : parent);
		try {	
			if (parent != null) {
				assertChild(p, child, null, type);
				final Subsystem s = getConstituentAsSubsystem(p, child, null, type);
				testDynamicImport(s);
			}
			else {
				testDynamicImport(p);
			}
		}
		finally {
			uninstallSubsystemSilently(p);
		}
	}
	
	private void testDynamicImport(Subsystem subsystem) throws Exception {
		testDynamicImport(subsystem, "org.osgi.framework.Constants");
	}
	
	private void testDynamicImport(Subsystem subsystem, String clazz) throws Exception {
		assertConstituent(subsystem, BUNDLE_A);
		Bundle bundleA = getConstituentAsBundle(subsystem, BUNDLE_A, null, null);
		bundleA.loadClass("a.A");
		assertTrue("Weaving hook not called", weavingHookCalled.get());
		try {
			bundleA.loadClass(clazz);
		}
		catch (ClassNotFoundException e) {
			e.printStackTrace();
			fail("Dynamic import not visible");
		}
	}
	
	private void testSharingPolicy(Subsystem subsystem, String dynamicImport, boolean allowed) {
		Region region = getRegion(subsystem);
		Set<FilteredRegion> filteredRegions = region.getEdges();
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(PackageNamespace.PACKAGE_NAMESPACE, dynamicImport);
		map.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, Version.emptyVersion);
		boolean wasAllowed = false;
		for (FilteredRegion filteredRegion : filteredRegions) {
			RegionFilter filter = filteredRegion.getFilter();
			if (allowed) {
				if (filter.isAllowed(PackageNamespace.PACKAGE_NAMESPACE, map)) {
					wasAllowed = true;
					break;
				}
			}
			else {
				assertFalse("Sharing policy should not have been updated", 
						filter.isAllowed(PackageNamespace.PACKAGE_NAMESPACE, map));
			}
		}
		if (allowed && !wasAllowed) {
			fail("Sharing policy should have been updated");
		}
	}
	
	@Test
	public void testConnectedNonSubsystemRegions() throws Exception {
		registerWeavingHook("b");
		Bundle bundleB = getRootSubsystem().getBundleContext().installBundle(
				BUNDLE_B, new ByteArrayInputStream(createBundleBContent()));
		uninstallableBundles.add(bundleB);
		Subsystem applicationA = installSubsystemFromFile(APPLICATION_A);
		uninstallableSubsystems.add(applicationA);
		Subsystem applicationB = getChild(applicationA, APPLICATION_B);
		uninstallSubsystem(applicationB);
		removeConnectionWithParent(applicationA);
		Region region = getRegion(applicationA);
		RegionDigraph digraph = region.getRegionDigraph();
		Region r1 = digraph.createRegion("R1");
		deletableRegions.add(r1);
		region.connectRegion(r1, digraph.createRegionFilterBuilder().allow("y", "(y=x)").build());
		Region r2a = digraph.createRegion("R2A");
		deletableRegions.add(r2a);
		Bundle bundleB1 = r2a.installBundleAtLocation(BUNDLE_B + '1', new ByteArrayInputStream(createBundleBContent()));
		uninstallableBundles.add(bundleB1);
		Region r2b = digraph.createRegion("R2B");
		deletableRegions.add(r2b);
		r2b.connectRegion(r2a, digraph.createRegionFilterBuilder().allow("osgi.wiring.package", "(&(osgi.wiring.package=b)(version=0))").build());
		region.connectRegion(r2b, digraph.createRegionFilterBuilder().allow("osgi.wiring.package", "(&(osgi.wiring.package=b)(version=0))").build());
		applicationB = installSubsystemFromFile(applicationA, APPLICATION_B);
		uninstallableSubsystems.add(applicationB);
		try {
			testDynamicImport(applicationB, "b.B");
		}
		catch (AssertionError e) {
			fail("Dynamic import should have succeeded");
		}
		testSharingPolicy(applicationB, "b", true);
		testSharingPolicy(applicationA, "b", true);
		testSharingPolicy(getRootSubsystem(), "b", false);
	}
}
