package org.osgi.service.indexer.impl;

import static java.util.Collections.singletonList;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import org.osgi.service.indexer.Builder;
import org.osgi.service.indexer.Capability;
import org.osgi.service.indexer.Namespaces;
import org.osgi.service.indexer.Requirement;
import org.osgi.service.indexer.Resource;
import org.osgi.service.indexer.ResourceAnalyzer;
import org.osgi.service.indexer.impl.types.SymbolicName;
import org.osgi.service.indexer.impl.types.VersionKey;
import org.osgi.service.indexer.impl.types.VersionRange;
import org.osgi.service.indexer.impl.util.Hex;
import org.osgi.service.indexer.impl.util.OSGiHeader;
import org.osgi.service.indexer.impl.util.Yield;
import org.osgi.service.log.LogService;

public class BundleAnalyzer implements ResourceAnalyzer {
	private static URI							cwd							= new File("").toURI().normalize();

	private static final String					SHA_256						= "SHA-256";

	// Duplicate these constants here to avoid a compile-time dependency on OSGi
	// R4.3
	private static final String					PROVIDE_CAPABILITY			= "Provide-Capability";
	private static final String					REQUIRE_CAPABILITY			= "Require-Capability";

	// Obsolete OSGi constants
	private static final String					IMPORT_SERVICE_AVAILABILITY	= "availability:";

	// Filename suffix for JAR files
	private static final String					SUFFIX_JAR					= ".jar";

	private final ThreadLocal<GeneratorState>	state						= new ThreadLocal<GeneratorState>();
	@SuppressWarnings("unused")
	private final LogService					log;

	public BundleAnalyzer(LogService log) {
		this.log = log;
	}

	public void analyzeResource(Resource resource, List<Capability> capabilities, List<Requirement> requirements)
			throws Exception {
		MimeType mimeType = Util.getMimeType(resource);
		if (mimeType == MimeType.Bundle || mimeType == MimeType.Fragment) {
			doBundleIdentity(resource, mimeType, capabilities);
			doContent(resource, mimeType, capabilities);
			doBundleAndHost(resource, capabilities);
			doExports(resource, capabilities);
			doImports(resource, requirements);
			doRequireBundles(resource, requirements);
			doFragment(resource, requirements);
			doExportService(resource, capabilities);
			doImportService(resource, requirements);
			doBREE(resource, requirements);
			doCapabilities(resource, capabilities);
			doRequirements(resource, requirements);
			doBundleNativeCode(resource, requirements);
		} else {
			doPlainJarIdentity(resource, capabilities);
			doContent(resource, mimeType, capabilities);
		}
	}

	private void doBundleIdentity(Resource resource, MimeType mimeType, List< ? super Capability> caps)
			throws Exception {
		Manifest manifest = resource.getManifest();
		if (manifest == null)
			throw new IllegalArgumentException("Missing bundle manifest.");

		String type;
		switch (mimeType) {
			case Bundle :
				type = Namespaces.RESOURCE_TYPE_BUNDLE;
				break;
			case Fragment :
				type = Namespaces.RESOURCE_TYPE_FRAGMENT;
				break;
			default :
				type = Namespaces.RESOURCE_TYPE_PLAIN_JAR;
				break;
		}

		SymbolicName bsn = Util.getSymbolicName(resource);
		boolean singleton = Boolean.TRUE.toString()
				.equalsIgnoreCase(bsn.getAttributes().get(Constants.SINGLETON_DIRECTIVE + ":"));

		Version version = Util.getVersion(resource);

		Builder builder = new Builder().setNamespace(Namespaces.NS_IDENTITY)
				.addAttribute(Namespaces.NS_IDENTITY, bsn.getName())
				.addAttribute(Namespaces.ATTR_IDENTITY_TYPE, type)
				.addAttribute(Namespaces.ATTR_VERSION, version);
		if (singleton)
			builder.addDirective(Namespaces.DIRECTIVE_SINGLETON, Boolean.TRUE.toString());
		caps.add(builder.buildCapability());
	}

	private void doPlainJarIdentity(Resource resource, List< ? super Capability> caps) {
		String name = (String) resource.getProperties().get(Resource.NAME);
		if (name.toLowerCase().endsWith(SUFFIX_JAR))
			name = name.substring(0, name.length() - SUFFIX_JAR.length());

		Version version = null;
		int dashIndex = name.lastIndexOf('-');
		if (dashIndex > 0) {
			try {
				String versionStr = name.substring(dashIndex + 1);
				version = new Version(versionStr);
				name = name.substring(0, dashIndex);
			} catch (Exception e) {
				version = null;
			}
		}

		Builder builder = new Builder().setNamespace(Namespaces.NS_IDENTITY)
				.addAttribute(Namespaces.NS_IDENTITY, name)
				.addAttribute(Namespaces.ATTR_IDENTITY_TYPE, Namespaces.RESOURCE_TYPE_PLAIN_JAR);
		if (version != null)
			builder.addAttribute(Namespaces.ATTR_VERSION, version);
		caps.add(builder.buildCapability());
	}

	void setStateLocal(GeneratorState state) {
		this.state.set(state);
	}

	private GeneratorState getStateLocal() {
		return state.get();
	}

	private void doContent(Resource resource, MimeType mimeType, List< ? super Capability> capabilities)
			throws Exception {
		String sha = calculateSHA(resource);
		List<String> locations = calculateLocation(resource);

		for (String location : locations) {
			Builder builder = new Builder().setNamespace(Namespaces.NS_CONTENT);

			builder.addAttribute(Namespaces.NS_CONTENT, sha);

			builder.addAttribute(Namespaces.ATTR_CONTENT_URL, location);

			long size = resource.getSize();
			if (size > 0L)
				builder.addAttribute(Namespaces.ATTR_CONTENT_SIZE, size);

			builder.addAttribute(Namespaces.ATTR_CONTENT_MIME, mimeType.toString());

			capabilities.add(builder.buildCapability());
		}
	}

	private String calculateSHA(Resource resource) throws IOException, NoSuchAlgorithmException {
		MessageDigest digest = MessageDigest.getInstance(SHA_256);
		byte[] buf = new byte[1024];

		try (InputStream stream = resource.getStream()) {
			while (true) {
				int bytesRead = stream.read(buf, 0, 1024);
				if (bytesRead < 0)
					break;

				digest.update(buf, 0, bytesRead);
			}
		}

		return Hex.toHexString(digest.digest());
	}

	private List<String> calculateLocation(Resource resource) throws IOException, URISyntaxException {
		File file = new File(resource.getLocation()).getAbsoluteFile();
		URI normalizedUri = file.toURI().normalize();
		File normalizedFile = Paths.get(normalizedUri).toFile();

		GeneratorState state = getStateLocal();
		if (state == null) {
			return singletonList(cwd.relativize(normalizedUri).toString());
		} else {

			//
			// Check if a resolver was specified
			// If so, we give that preference over the automatic
			// and template calculation
			//

			List<URLResolver> resolvers = state.getResolvers();
			if (resolvers != null && !resolvers.isEmpty()) {
				List<String> uris = new ArrayList<>();
				for (URLResolver resolver : resolvers) {
					try {
						URI uri = resolver.resolver(file);
						if (uri != null) {
							uris.add(uri.toString());
						} else {
							log.log(LogService.LOG_DEBUG,
									"Resolver " + resolver + " had no output for " + normalizedUri);
						}
					} catch (Exception e) {
						if (log != null)
							log.log(LogService.LOG_ERROR, "Resolver " + resolver + " failed on " + normalizedUri);
					}
				}
				if (uris.isEmpty()) {
					log.log(LogService.LOG_INFO, "No URIs found by URI resolvers, falling back to old method");
				} else {
					return uris;
				}
			}

			URI root = state.getRootUrl();
			URI absoluteDir = normalizedFile.getParentFile().toURI();
			URI relativeDir = root.relativize(absoluteDir);

			if (relativeDir == absoluteDir)
				throw new IllegalArgumentException(
						"Cannot index files above the root URL. Root = " + root + " path is " + normalizedFile);

			String fileName = normalizedFile.getName();
			String urlTemplate = state.getUrlTemplate();

			if (urlTemplate != null) {
				String bsn = (urlTemplate.indexOf("%s") == -1) ? "" : Util.getSymbolicName(resource).getName();
				Version version = (urlTemplate.indexOf("%v") == -1) ? Version.emptyVersion : Util.getVersion(resource);
				urlTemplate = urlTemplate.replaceAll("%s", "%1\\$s")
						.replaceAll("%f", "%2\\$s")
						.replaceAll("%p", "%3\\$s")
						.replaceAll("%v", "%4\\$s");
				return singletonList(String.format(urlTemplate, bsn, fileName, relativeDir.toString(), version));
			} else {
				return singletonList(relativeDir.resolve(fileName).toString());
			}
		}
	}

	private void doBundleAndHost(Resource resource, List< ? super Capability> caps) throws Exception {
		Builder bundleBuilder = new Builder().setNamespace(Namespaces.NS_WIRING_BUNDLE);
		Builder hostBuilder = new Builder().setNamespace(Namespaces.NS_WIRING_HOST);
		boolean allowFragments = true;

		Attributes attribs = resource.getManifest().getMainAttributes();
		if (attribs.getValue(Constants.FRAGMENT_HOST) != null)
			return;

		SymbolicName bsn = Util.getSymbolicName(resource);
		Version version = Util.getVersion(resource);

		bundleBuilder.addAttribute(Namespaces.NS_WIRING_BUNDLE, bsn.getName())
				.addAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE, version);
		hostBuilder.addAttribute(Namespaces.NS_WIRING_HOST, bsn.getName())
				.addAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE, version);

		for (Entry<String,String> attribEntry : bsn.getAttributes().entrySet()) {
			String key = attribEntry.getKey();
			if (key.endsWith(":")) {
				String directiveName = key.substring(0, key.length() - 1);
				if (Constants.FRAGMENT_ATTACHMENT_DIRECTIVE.equalsIgnoreCase(directiveName)) {
					if (Constants.FRAGMENT_ATTACHMENT_NEVER.equalsIgnoreCase(attribEntry.getValue()))
						allowFragments = false;
				} else if (!Constants.SINGLETON_DIRECTIVE.equalsIgnoreCase(directiveName)) {
					bundleBuilder.addDirective(directiveName, attribEntry.getValue());
				}
			} else {
				bundleBuilder.addAttribute(key, attribEntry.getValue());
			}
		}

		caps.add(bundleBuilder.buildCapability());
		if (allowFragments)
			caps.add(hostBuilder.buildCapability());
	}

	private void doExports(Resource resource, List< ? super Capability> caps) throws Exception {
		Manifest manifest = resource.getManifest();

		String exportsStr = manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
		Map<String,Map<String,String>> exports = OSGiHeader.parseHeader(exportsStr);
		for (Entry<String,Map<String,String>> entry : exports.entrySet()) {
			Builder builder = new Builder().setNamespace(Namespaces.NS_WIRING_PACKAGE);

			String pkgName = OSGiHeader.removeDuplicateMarker(entry.getKey());
			builder.addAttribute(Namespaces.NS_WIRING_PACKAGE, pkgName);

			String versionStr = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
			Version version = (versionStr != null) ? new Version(versionStr) : new Version(0, 0, 0);
			builder.addAttribute(Namespaces.ATTR_VERSION, version);

			for (Entry<String,String> attribEntry : entry.getValue().entrySet()) {
				String key = attribEntry.getKey();
				if (!"specification-version".equalsIgnoreCase(key)
						&& !Constants.VERSION_ATTRIBUTE.equalsIgnoreCase(key)) {
					if (key.endsWith(":"))
						builder.addDirective(key.substring(0, key.length() - 1), attribEntry.getValue());
					else
						builder.addAttribute(key, attribEntry.getValue());
				}
			}

			SymbolicName bsn = Util.getSymbolicName(resource);
			builder.addAttribute(Namespaces.ATTR_BUNDLE_SYMBOLIC_NAME, bsn.getName());
			Version bundleVersion = Util.getVersion(resource);
			builder.addAttribute(Namespaces.ATTR_BUNDLE_VERSION, bundleVersion);

			caps.add(builder.buildCapability());
		}
	}

	private void doImports(Resource resource, List< ? super Requirement> reqs) throws Exception {
		Manifest manifest = resource.getManifest();

		String importsStr = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
		Map<String,Map<String,String>> imports = OSGiHeader.parseHeader(importsStr);
		for (Entry<String,Map<String,String>> entry : imports.entrySet()) {
			StringBuilder filter = new StringBuilder();

			String pkgName = OSGiHeader.removeDuplicateMarker(entry.getKey());
			filter.append("(osgi.wiring.package=").append(pkgName).append(")");

			String versionStr = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
			if (versionStr != null) {
				VersionRange version = new VersionRange(versionStr);
				filter.insert(0, "(&");
				Util.addVersionFilter(filter, version, VersionKey.PackageVersion);
				filter.append(")");
			}

			Builder builder = new Builder().setNamespace(Namespaces.NS_WIRING_PACKAGE)
					.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString());

			copyAttribsAndDirectives(entry.getValue(), builder, Constants.VERSION_ATTRIBUTE, "specification-version");

			reqs.add(builder.buildRequirement());
		}
	}

	private void copyAttribsAndDirectives(Map<String,String> input, Builder output, String... ignores) {
		Set<String> ignoreSet = new HashSet<String>(Arrays.asList(ignores));

		for (Entry<String,String> entry : input.entrySet()) {
			String key = entry.getKey();
			if (!ignoreSet.contains(key)) {
				if (key.endsWith(":")) {
					String directive = key.substring(0, key.length() - 1);
					output.addDirective(directive, entry.getValue());
				} else {
					output.addAttribute(key, entry.getValue());
				}
			}
		}
	}

	private void doRequireBundles(Resource resource, List< ? super Requirement> reqs) throws Exception {
		Manifest manifest = resource.getManifest();

		String requiresStr = manifest.getMainAttributes().getValue(Constants.REQUIRE_BUNDLE);
		if (requiresStr == null)
			return;

		Map<String,Map<String,String>> requires = OSGiHeader.parseHeader(requiresStr);
		for (Entry<String,Map<String,String>> entry : requires.entrySet()) {
			StringBuilder filter = new StringBuilder();

			String bsn = OSGiHeader.removeDuplicateMarker(entry.getKey());
			filter.append("(osgi.wiring.bundle=").append(bsn).append(")");

			String versionStr = entry.getValue().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
			if (versionStr != null) {
				VersionRange version = new VersionRange(versionStr);
				filter.insert(0, "(&");
				Util.addVersionFilter(filter, version, VersionKey.BundleVersion);
				filter.append(")");
			}

			Builder builder = new Builder().setNamespace(Namespaces.NS_WIRING_BUNDLE)
					.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString());

			copyAttribsAndDirectives(entry.getValue(), builder, Constants.BUNDLE_VERSION_ATTRIBUTE);

			reqs.add(builder.buildRequirement());
		}
	}

	private void doFragment(Resource resource, List< ? super Requirement> reqs) throws Exception {
		Manifest manifest = resource.getManifest();
		String fragmentHost = manifest.getMainAttributes().getValue(Constants.FRAGMENT_HOST);

		if (fragmentHost != null) {
			StringBuilder filter = new StringBuilder();
			Map<String,Map<String,String>> fragmentList = OSGiHeader.parseHeader(fragmentHost);
			if (fragmentList.size() != 1)
				throw new IllegalArgumentException("Invalid Fragment-Host header: cannot contain multiple entries");
			Entry<String,Map<String,String>> entry = fragmentList.entrySet().iterator().next();

			String bsn = entry.getKey();
			filter.append("(&(osgi.wiring.host=").append(bsn).append(")");

			String versionStr = entry.getValue().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
			VersionRange version = versionStr != null ? new VersionRange(versionStr)
					: new VersionRange(Version.emptyVersion.toString());
			Util.addVersionFilter(filter, version, VersionKey.BundleVersion);
			filter.append(")");

			Builder builder = new Builder().setNamespace(Namespaces.NS_WIRING_HOST)
					.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString());

			reqs.add(builder.buildRequirement());
		}
	}

	private void doExportService(Resource resource, List< ? super Capability> caps) throws Exception {
		@SuppressWarnings("deprecation")
		String exportsStr = resource.getManifest().getMainAttributes().getValue(Constants.EXPORT_SERVICE);
		Map<String,Map<String,String>> exports = OSGiHeader.parseHeader(exportsStr);

		for (Entry<String,Map<String,String>> export : exports.entrySet()) {
			String service = OSGiHeader.removeDuplicateMarker(export.getKey());
			Builder builder = new Builder().setNamespace(Namespaces.NS_SERVICE).addAttribute(Constants.OBJECTCLASS,
					service);
			for (Entry<String,String> attribEntry : export.getValue().entrySet())
				builder.addAttribute(attribEntry.getKey(), attribEntry.getValue());
			builder.addDirective(Namespaces.DIRECTIVE_EFFECTIVE, Namespaces.EFFECTIVE_ACTIVE);
			caps.add(builder.buildCapability());
		}
	}

	private void doImportService(Resource resource, List< ? super Requirement> reqs) throws Exception {
		@SuppressWarnings("deprecation")
		String importsStr = resource.getManifest().getMainAttributes().getValue(Constants.IMPORT_SERVICE);
		Map<String,Map<String,String>> imports = OSGiHeader.parseHeader(importsStr);

		for (Entry<String,Map<String,String>> imp : imports.entrySet()) {
			String service = OSGiHeader.removeDuplicateMarker(imp.getKey());
			Map<String,String> attribs = imp.getValue();

			boolean optional = false;
			String availabilityStr = attribs.get(IMPORT_SERVICE_AVAILABILITY);
			if (Constants.RESOLUTION_OPTIONAL.equals(availabilityStr))
				optional = true;

			StringBuilder filter = new StringBuilder();
			filter.append('(').append(Constants.OBJECTCLASS).append('=').append(service).append(')');

			Builder builder = new Builder().setNamespace(Namespaces.NS_SERVICE)
					.addDirective(Namespaces.DIRECTIVE_FILTER, filter.toString())
					.addDirective(Namespaces.DIRECTIVE_EFFECTIVE, Namespaces.EFFECTIVE_ACTIVE);
			if (optional)
				builder.addDirective(Namespaces.DIRECTIVE_RESOLUTION, Constants.RESOLUTION_OPTIONAL);
			reqs.add(builder.buildRequirement());
		}
	}

	private void doBREE(Resource resource, List< ? super Requirement> reqs) throws Exception {
		@SuppressWarnings("deprecation")
		String breeStr = resource.getManifest()
				.getMainAttributes()
				.getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
		Map<String,Map<String,String>> brees = OSGiHeader.parseHeader(breeStr);

		final String filter;
		if (!brees.isEmpty()) {
			if (brees.size() == 1) {
				String bree = brees.keySet().iterator().next();
				filter = EE.parseBREE(bree).toFilter();
			} else {
				StringBuilder builder = new StringBuilder().append("(|");
				for (String bree : brees.keySet()) {
					bree = OSGiHeader.removeDuplicateMarker(bree);
					builder.append(EE.parseBREE(bree).toFilter());
				}
				builder.append(')');
				filter = builder.toString();
			}

			Requirement requirement = new Builder().setNamespace(Namespaces.NS_EE)
					.addDirective(Namespaces.DIRECTIVE_FILTER, filter)
					.buildRequirement();
			reqs.add(requirement);
		}
	}

	private void doCapabilities(Resource resource, final List< ? super Capability> caps) throws Exception {
		String capsStr = resource.getManifest().getMainAttributes().getValue(PROVIDE_CAPABILITY);
		buildFromHeader(capsStr, new Yield<Builder>() {
			public void yield(Builder builder) {
				caps.add(builder.buildCapability());
			}
		});
	}

	private void doRequirements(Resource resource, final List< ? super Requirement> reqs) throws IOException {
		String reqsStr = resource.getManifest().getMainAttributes().getValue(REQUIRE_CAPABILITY);
		buildFromHeader(reqsStr, new Yield<Builder>() {
			public void yield(Builder builder) {
				reqs.add(builder.buildRequirement());
			}
		});
	}

	private void doBundleNativeCode(Resource resource, final List< ? super Requirement> reqs) throws IOException {
		String nativeHeaderStr = resource.getManifest().getMainAttributes().getValue(Constants.BUNDLE_NATIVECODE);
		if (nativeHeaderStr == null)
			return;

		boolean optional = false;
		List<String> options = new LinkedList<String>();

		Map<String,Map<String,String>> nativeHeader = OSGiHeader.parseHeader(nativeHeaderStr);
		for (Entry<String,Map<String,String>> entry : nativeHeader.entrySet()) {
			String name = entry.getKey();
			if ("*".equals(name)) {
				optional = true;
				continue;
			}

			StringBuilder builder = new StringBuilder().append("(&");
			Map<String,String> attribs = entry.getValue();

			String osnamesFilter = buildFilter(attribs, Constants.BUNDLE_NATIVECODE_OSNAME,
					Namespaces.ATTR_NATIVE_OSNAME);
			if (osnamesFilter != null)
				builder.append(osnamesFilter);

			String versionRangeStr = attribs.get(Constants.BUNDLE_NATIVECODE_OSVERSION);
			if (versionRangeStr != null)
				Util.addVersionFilter(builder, new VersionRange(versionRangeStr), VersionKey.NativeOsVersion);

			String processorFilter = buildFilter(attribs, Constants.BUNDLE_NATIVECODE_PROCESSOR,
					Namespaces.ATTR_NATIVE_PROCESSOR);
			if (processorFilter != null)
				builder.append(processorFilter);

			String languageFilter = buildFilter(attribs, Constants.BUNDLE_NATIVECODE_LANGUAGE,
					Namespaces.ATTR_NATIVE_LANGUAGE);
			if (languageFilter != null)
				builder.append(languageFilter);

			String selectionFilter = attribs.get(Constants.SELECTION_FILTER_ATTRIBUTE);
			if (selectionFilter != null)
				builder.append(selectionFilter);

			builder.append(")");
			options.add(builder.toString());
		}
		if (options.isEmpty())
			return;

		String filter;
		if (options.size() == 1)
			filter = options.get(0);
		else {
			StringBuilder builder = new StringBuilder();
			builder.append("(|");
			for (String option : options)
				builder.append(option);
			builder.append(")");
			filter = builder.toString();
		}

		Builder builder = new Builder().setNamespace(Namespaces.NS_NATIVE).addDirective(Namespaces.DIRECTIVE_FILTER,
				filter);
		if (optional)
			builder.addDirective(Namespaces.DIRECTIVE_RESOLUTION, Namespaces.RESOLUTION_OPTIONAL);
		reqs.add(builder.buildRequirement());
	}

	/*
	 * Assemble a compound filter by searching a map of attributes. E.g. the
	 * following values: 1. foo=bar 2. foo=baz 3. foo=quux become the filter
	 * (|(foo~=bar)(foo~=baz)(foo~=quux)). Note that the duplicate foo keys will
	 * have trailing tildes as duplicate markers, these will be removed.
	 */
	private String buildFilter(Map<String,String> attribs, String match, String filterKey) {
		List<String> options = new LinkedList<String>();
		for (Entry<String,String> entry : attribs.entrySet()) {
			String key = OSGiHeader.removeDuplicateMarker(entry.getKey());
			if (match.equals(key)) {
				String filter = String.format("(%s~=%s)", filterKey, entry.getValue());
				options.add(filter);
			}
		}

		if (options.isEmpty())
			return null;
		if (options.size() == 1)
			return options.get(0);

		StringBuilder builder = new StringBuilder();
		builder.append("(|");
		for (String option : options)
			builder.append(option);
		builder.append(")");

		return builder.toString();
	}

	private static void buildFromHeader(String headerStr, Yield<Builder> output) {
		if (headerStr == null)
			return;
		Map<String,Map<String,String>> header = OSGiHeader.parseHeader(headerStr);

		for (Entry<String,Map<String,String>> entry : header.entrySet()) {
			String namespace = OSGiHeader.removeDuplicateMarker(entry.getKey());
			Builder builder = new Builder().setNamespace(namespace);

			Map<String,String> attribs = entry.getValue();
			Util.copyAttribsToBuilder(builder, attribs);
			output.yield(builder);
		}
	}

}
