/*******************************************************************************
 * Copyright (c) 2008, 2013 Martin Lippert and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *   David Knibb               initial implementation      
 *   Martin Lippert            supplementing mechanism reworked     
 *   Martin Lippert            fragment handling fixed
 *******************************************************************************/

package org.eclipse.equinox.service.weaving;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.Bundle;

/**
 * A supplementer object is created for every bundle that contains one or many
 * of the supplementer headers in its header.
 * 
 * The corresponding supplementer object contains the information which headers
 * the bundle defines and which bundles it supplements in the running system.
 * 
 * @author Martin Lippert
 */
public class Supplementer {

	private final ManifestElement[] supplementBundle;

	private final Set<Bundle> supplementedBundles; // elements of type Bundle

	private final Bundle supplementer;

	private final Bundle supplementerHost;

	private final ManifestElement[] supplementExporter;

	private final ManifestElement[] supplementImporter;

	/**
	 * Creates a supplementer object for the given bundle.
	 * 
	 * @param bundle             The bundle that defines the supplementer headers
	 * @param bundleHost         The host bundle of the supplementer bundle, if the
	 *                           bundle is a fragment, otherwise null
	 * @param supplementBundle   The parsed manifest headers defined for
	 *                           Eclipse-SupplementBundle
	 * @param supplementImporter The parsed manifest headers defined for
	 *                           Eclipse-SupplementImporter
	 * @param supplementExporter The parsed manifest headers defined for
	 *                           Eclipse-SupplementExporter
	 */
	public Supplementer(final Bundle bundle, final Bundle bundleHost, final ManifestElement[] supplementBundle,
			final ManifestElement[] supplementImporter, final ManifestElement[] supplementExporter) {
		this.supplementer = bundle;
		this.supplementerHost = bundleHost != null ? bundleHost : bundle;
		this.supplementBundle = supplementBundle;
		this.supplementImporter = supplementImporter;
		this.supplementExporter = supplementExporter;
		this.supplementedBundles = new HashSet<>();
	}

	/**
	 * Add a bundle to the list of supplemented bundles
	 * 
	 * @param supplementedBundle The bundle that is supplemented by this
	 *                           supplementer
	 */
	public void addSupplementedBundle(final Bundle supplementedBundle) {
		this.supplementedBundles.add(supplementedBundle);
	}

	// knibb
	// test if two Strings are equal
	// with wild card support - only supports strings ending in *
	private boolean equals_wild(final String input, final String match) {
		if (input.equals(match)) {
			// its a match so just return true
			return true;
		}
		if (input.endsWith("*") == false) { //$NON-NLS-1$
			// no wildcards are being used here
			return false;
		}
		final String wild_in = input.substring(0, input.length() - 1);
		if (match.startsWith(wild_in))
			return true;

		return false;
	}

	/**
	 * Gives information about which bundles are currently supplemented by this
	 * supplementer
	 * 
	 * @return The currently supplemented bundles
	 */
	public Bundle[] getSupplementedBundles() {
		return supplementedBundles.toArray(new Bundle[supplementedBundles.size()]);
	}

	/**
	 * Returns the bundle that defines the supplementer headers (this supplementer
	 * object belongs to)
	 * 
	 * @return The bundle object this supplementer belongs to
	 */
	public Bundle getSupplementerBundle() {
		return supplementer;
	}

	/**
	 * Returns the host of the supplementer bundle, if it is a fragment, otherwise
	 * this returns the same as getSupplementerBundle()
	 * 
	 * @return The host bundle this supplementer belongs to
	 */
	public Bundle getSupplementerHost() {
		return supplementerHost;
	}

	/**
	 * The symbolic name of the supplementer bundle
	 * 
	 * @return The symbolic name of the supplementer bundle
	 */
	public String getSymbolicName() {
		return supplementer.getSymbolicName();
	}

	/**
	 * Provides information about whether a given bundle is supplemented by this
	 * supplementer or not
	 * 
	 * @param bundle The bundle that might be supplemented by this supplementer
	 * @return true, if the bundle is supplemented by this supplementer, otherwise
	 *         false
	 */
	public boolean isSupplemented(final Bundle bundle) {
		return supplementedBundles.contains(bundle);
	}

	/**
	 * Checks if the given export-package header definitions matches the
	 * supplement-exporter definitions of this supplementer
	 * 
	 * @param exports The headers to check for matching against this supplementer
	 * @return true, if this supplementer matches against the given export-package
	 *         headers
	 */
	public boolean matchesSupplementExporter(final ManifestElement[] exports) {
		boolean matches = false;

		if (supplementExporter != null)
			for (int i = 0; !matches && i < supplementExporter.length; i++) {
				final ManifestElement supplementExport = supplementExporter[i];
				for (int j = 0; !matches && j < exports.length; j++) {
					final ManifestElement exportPackage = exports[j];
					if (supplementExport.getValue().equals(exportPackage.getValue()))
						matches = true;
				}
			}

		return matches;
	}

	/**
	 * Checks if the given import-package header definitions matches the
	 * supplement-importer definitions of this supplementer
	 * 
	 * @param imports The headers to check for matching against this supplementer
	 * @return true, if this supplementer matches against the given import-package
	 *         headers
	 */
	public boolean matchesSupplementImporter(final ManifestElement[] imports) {
		boolean matches = false;

		if (supplementImporter != null)
			for (int i = 0; !matches && i < supplementImporter.length; i++) {
				final ManifestElement supplementImport = supplementImporter[i];
				for (int j = 0; !matches && j < imports.length; j++) {
					final ManifestElement importPackage = imports[j];
					if (supplementImport.getValue().equals(importPackage.getValue()))
						matches = true;
				}
			}

		return matches;
	}

	/**
	 * Checks if the given bundle symbolic name definition matches the
	 * supplement-bundle definition of this supplementer
	 * 
	 * @param symbolicName The symbolic name of the bundle that shoudl be checked
	 * @return true, if this supplementer matches against the given bundle symbolic
	 *         name
	 */
	public boolean matchSupplementer(final String symbolicName) {
		boolean matches = false;

		if (supplementBundle != null)
			for (int i = 0; !matches && i < supplementBundle.length; i++) {
				final ManifestElement bundle = supplementBundle[i];
				if (equals_wild(bundle.getValue(), symbolicName))
					matches = true;
			}

		return matches;
	}

	/**
	 * Removes the given bundle from the set of supplemented bundles (that are
	 * supplemented by this supplementer)
	 * 
	 * @param supplementedBundle The bundle that is no longer supplemented by this
	 *                           supplementer
	 */
	public void removeSupplementedBundle(final Bundle supplementedBundle) {
		this.supplementedBundles.remove(supplementedBundle);
	}

}
