/*******************************************************************************
 * 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);
    }

}
