/*
 * 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.felix.bundlerepository.impl;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.felix.bundlerepository.Capability;
import org.apache.felix.bundlerepository.DataModelHelper;
import org.apache.felix.bundlerepository.Property;
import org.apache.felix.bundlerepository.Repository;
import org.apache.felix.bundlerepository.Requirement;
import org.apache.felix.bundlerepository.Resource;
import org.apache.felix.utils.filter.FilterImpl;
import org.apache.felix.utils.manifest.Attribute;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Directive;
import org.apache.felix.utils.manifest.Parser;
import org.apache.felix.utils.version.VersionCleaner;
import org.apache.felix.utils.version.VersionRange;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;

public class DataModelHelperImpl implements DataModelHelper
{

    public static final String BUNDLE_LICENSE = "Bundle-License";
    public static final String BUNDLE_SOURCE = "Bundle-Source";

    public Requirement requirement(String name, String filter)
    {
        RequirementImpl req = new RequirementImpl();
        req.setName(name);
        if (filter != null)
        {
            req.setFilter(filter);
        }
        return req;
    }

    public Filter filter(String filter)
    {
        try
        {
            return FilterImpl.newInstance(filter);
        }
        catch (InvalidSyntaxException e)
        {
            IllegalArgumentException ex = new IllegalArgumentException();
            ex.initCause(e);
            throw ex;
        }
    }

    public Repository repository(final URL url) throws Exception
    {
        InputStream is = null;
        BufferedReader br = null;

        try
        {
            if (url.getPath().endsWith(".zip"))
            {
                ZipInputStream zin = new ZipInputStream(FileUtil.openURL(url));
                ZipEntry entry = zin.getNextEntry();
                while (entry != null)
                {
                    if (entry.getName().equals("repository.xml"))
                    {
                        is = zin;
                        break;
                    }
                    entry = zin.getNextEntry();
                }
            }
            else if (url.getPath().endsWith(".gz"))
            {
                is = new GZIPInputStream(FileUtil.openURL(url));                    
            }
            else
            {
                is = FileUtil.openURL(url);
            }

            if (is != null)
            {
                RepositoryImpl repository = repository(is);
                repository.setURI(url.toExternalForm());
                return repository;
            }
            else
            {
                // This should not happen.
                throw new Exception("Unable to get input stream for repository.");
            }
        }
        finally
        {
            try
            {
                if (is != null)
                {
                    is.close();
                }
            }
            catch (IOException ex)
            {
                // Not much we can do.
            }
        }
    }

    public RepositoryImpl repository(InputStream is) throws Exception
    {
        RepositoryParser parser = RepositoryParser.getParser();
        RepositoryImpl repository = parser.parseRepository(is);
        return repository;
    }

    public Repository repository(Resource[] resources)
    {
        return new RepositoryImpl(resources);
    }

    public Capability capability(String name, Map properties)
    {
        CapabilityImpl cap = new CapabilityImpl(name);
        for (Iterator it = properties.entrySet().iterator(); it.hasNext();)
        {
            Map.Entry e = (Map.Entry) it.next();
            cap.addProperty((String) e.getKey(), (String) e.getValue());
        }
        return cap;
    }

    public String writeRepository(Repository repository)
    {
        try
        {
            StringWriter sw = new StringWriter();
            writeRepository(repository, sw);
            return sw.toString();
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public void writeRepository(Repository repository, Writer writer) throws IOException
    {
        XmlWriter w = new XmlWriter(writer);
        toXml(w, repository);
    }

    public String writeResource(Resource resource)
    {
        try
        {
            StringWriter sw = new StringWriter();
            writeResource(resource, sw);
            return sw.toString();
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public void writeResource(Resource resource, Writer writer) throws IOException
    {
        XmlWriter w = new XmlWriter(writer);
        toXml(w, resource);
    }

    public String writeCapability(Capability capability)
    {
        try
        {
            StringWriter sw = new StringWriter();
            writeCapability(capability, sw);
            return sw.toString();
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public void writeCapability(Capability capability, Writer writer) throws IOException
    {
        XmlWriter w = new XmlWriter(writer);
        toXml(w, capability);
    }

    public String writeRequirement(Requirement requirement)
    {
        try
        {
            StringWriter sw = new StringWriter();
            writeRequirement(requirement, sw);
            return sw.toString();
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public void writeRequirement(Requirement requirement, Writer writer) throws IOException
    {
        XmlWriter w = new XmlWriter(writer);
        toXml(w, requirement);
    }

    public String writeProperty(Property property)
    {
        try
        {
            StringWriter sw = new StringWriter();
            writeProperty(property, sw);
            return sw.toString();
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public void writeProperty(Property property, Writer writer) throws IOException
    {
        XmlWriter w = new XmlWriter(writer);
        toXml(w, property);
    }

    private static void toXml(XmlWriter w, Repository repository) throws IOException
    {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmss.SSS");
        w.element(RepositoryParser.REPOSITORY)
            .attribute(RepositoryParser.NAME, repository.getName())
            .attribute(RepositoryParser.LASTMODIFIED, format.format(new Date(repository.getLastModified())));

        if (repository instanceof RepositoryImpl)
        {
            Referral[] referrals = ((RepositoryImpl) repository).getReferrals();
            for (int i = 0; referrals != null && i < referrals.length; i++)
            {
                w.element(RepositoryParser.REFERRAL)
                    .attribute(RepositoryParser.DEPTH, new Integer(referrals[i].getDepth()))
                    .attribute(RepositoryParser.URL, referrals[i].getUrl())
                    .end();
            }
        }

        Resource[] resources = repository.getResources();
        for (int i = 0; resources != null && i < resources.length; i++)
        {
            toXml(w, resources[i]);
        }

        w.end();
    }

    private static void toXml(XmlWriter w, Resource resource) throws IOException
    {
        w.element(RepositoryParser.RESOURCE)
            .attribute(Resource.ID, resource.getId())
            .attribute(Resource.SYMBOLIC_NAME, resource.getSymbolicName())
            .attribute(Resource.PRESENTATION_NAME, resource.getPresentationName())
            .attribute(Resource.URI, getRelativeUri(resource, Resource.URI))
            .attribute(Resource.VERSION, resource.getVersion().toString());

        w.textElement(Resource.DESCRIPTION, resource.getProperties().get(Resource.DESCRIPTION))
            .textElement(Resource.SIZE, resource.getProperties().get(Resource.SIZE))
            .textElement(Resource.DOCUMENTATION_URI, getRelativeUri(resource, Resource.DOCUMENTATION_URI))
            .textElement(Resource.SOURCE_URI, getRelativeUri(resource, Resource.SOURCE_URI))
            .textElement(Resource.JAVADOC_URI, getRelativeUri(resource, Resource.JAVADOC_URI))
            .textElement(Resource.LICENSE_URI, getRelativeUri(resource, Resource.LICENSE_URI));

        String[] categories = resource.getCategories();
        for (int i = 0; categories != null && i < categories.length; i++)
        {
            w.element(RepositoryParser.CATEGORY)
                .attribute(RepositoryParser.ID, categories[i])
                .end();
        }
        Capability[] capabilities = resource.getCapabilities();
        for (int i = 0; capabilities != null && i < capabilities.length; i++)
        {
            toXml(w, capabilities[i]);
        }
        Requirement[] requirements = resource.getRequirements();
        for (int i = 0; requirements != null && i < requirements.length; i++)
        {
            toXml(w, requirements[i]);
        }
        w.end();
    }

    private static String getRelativeUri(Resource resource, String name) 
    {
        String uri = (String) resource.getProperties().get(name);
        if (resource instanceof ResourceImpl)
        {
            try
            {
                uri = URI.create(((ResourceImpl) resource).getRepository().getURI()).relativize(URI.create(uri)).toASCIIString();
            }
            catch (Throwable t)
            {
            }
        }
        return uri;
    }

    private static void toXml(XmlWriter w, Capability capability) throws IOException
    {
        w.element(RepositoryParser.CAPABILITY)
            .attribute(RepositoryParser.NAME, capability.getName());
        Property[] props = capability.getProperties();
        for (int j = 0; props != null && j < props.length; j++)
        {
            toXml(w, props[j]);
        }
        w.end();
    }

    private static void toXml(XmlWriter w, Property property) throws IOException
    {
        w.element(RepositoryParser.P)
            .attribute(RepositoryParser.N, property.getName())
            .attribute(RepositoryParser.T, property.getType())
            .attribute(RepositoryParser.V, property.getValue())
            .end();
    }

    private static void toXml(XmlWriter w, Requirement requirement) throws IOException
    {
        w.element(RepositoryParser.REQUIRE)
            .attribute(RepositoryParser.NAME, requirement.getName())
            .attribute(RepositoryParser.FILTER, requirement.getFilter())
            .attribute(RepositoryParser.EXTEND, Boolean.toString(requirement.isExtend()))
            .attribute(RepositoryParser.MULTIPLE, Boolean.toString(requirement.isMultiple()))
            .attribute(RepositoryParser.OPTIONAL, Boolean.toString(requirement.isOptional()))
            .text(requirement.getComment().trim())
            .end();
    }

    public Resource createResource(final Bundle bundle)
    {
        final Dictionary dict = bundle.getHeaders();
        return createResource(new Headers()
        {
            public String getHeader(String name)
            {
                return (String) dict.get(name);
            }
        });
    }

    public Resource createResource(final URL bundleUrl) throws IOException
    {
        ResourceImpl resource = createResource(new Headers()
        {
            private final Manifest manifest;
            private Properties localization;
            {
                // Do not use a JarInputStream so that we can read the manifest even if it's not
                // the first entry in the JAR.
                byte[] man = loadEntry(JarFile.MANIFEST_NAME);
                if (man == null)
                {
                    throw new IllegalArgumentException("The specified url is not a valid jar (can't read manifest): " + bundleUrl);
                }
                manifest = new Manifest(new ByteArrayInputStream(man));
            }
            public String getHeader(String name)
            {
                String value = manifest.getMainAttributes().getValue(name);
                if (value != null && value.startsWith("%"))
                {
                    if (localization == null)
                    {
                        try
                        {
                            localization = new Properties();
                            String path = manifest.getMainAttributes().getValue(Constants.BUNDLE_LOCALIZATION);
                            if (path == null)
                            {
                                path = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
                            }
                            path += ".properties";
                            byte[] loc = loadEntry(path);
                            if (loc != null)
                            {
                                localization.load(new ByteArrayInputStream(loc));
                            }
                        }
                        catch (IOException e)
                        {
                            // TODO: ?
                        }
                    }
                    value = value.substring(1);
                    value = localization.getProperty(value, value);
                }
                return value;
            }
            private byte[] loadEntry(String name) throws IOException
            {
                InputStream is = FileUtil.openURL(bundleUrl);
                try
                {
                    ZipInputStream jis = new ZipInputStream(is);
                    for (ZipEntry e = jis.getNextEntry(); e != null; e = jis.getNextEntry())
                    {
                        if (name.equalsIgnoreCase(e.getName()))
                        {
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            byte[] buf = new byte[1024];
                            int n;
                            while ((n = jis.read(buf, 0, buf.length)) > 0)
                            {
                                baos.write(buf, 0, n);
                            }
                            return baos.toByteArray();
                        }
                    }
                }
                finally
                {
                    is.close();
                }
                return null;
            }
        });
        if (resource != null)
        {
            if ("file".equals(bundleUrl.getProtocol()))
            {
                try {
                    File f = new File(bundleUrl.toURI());
                    resource.put(Resource.SIZE, Long.toString(f.length()), null);
                } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
                }
            }
            resource.put(Resource.URI, bundleUrl.toExternalForm(), null);
        }
        return resource;
    }

    public Resource createResource(final Attributes attributes)
    {
        return createResource(new Headers()
        {
            public String getHeader(String name)
            {
                return attributes.getValue(name);
            }
        });
    }

    public ResourceImpl createResource(Headers headers)
    {
        String bsn = headers.getHeader(Constants.BUNDLE_SYMBOLICNAME);
        if (bsn == null)
        {
            return null;
        }
        ResourceImpl resource = new ResourceImpl();
        populate(headers, resource);
        return resource;
    }

    static void populate(Headers headers, ResourceImpl resource)
    {
        String bsn = getSymbolicName(headers);
        String v = getVersion(headers);

        resource.put(Resource.ID, bsn + "/" + v);
        resource.put(Resource.SYMBOLIC_NAME, bsn);
        resource.put(Resource.VERSION, v);
        if (headers.getHeader(Constants.BUNDLE_NAME) != null)
        {
            resource.put(Resource.PRESENTATION_NAME, headers.getHeader(Constants.BUNDLE_NAME));
        }
        if (headers.getHeader(Constants.BUNDLE_DESCRIPTION) != null)
        {
            resource.put(Resource.DESCRIPTION, headers.getHeader(Constants.BUNDLE_DESCRIPTION));
        }
        if (headers.getHeader(BUNDLE_LICENSE) != null)
        {
            resource.put(Resource.LICENSE_URI, headers.getHeader(BUNDLE_LICENSE));
        }
        if (headers.getHeader(Constants.BUNDLE_COPYRIGHT) != null)
        {
            resource.put(Resource.COPYRIGHT, headers.getHeader(Constants.BUNDLE_COPYRIGHT));
        }
        if (headers.getHeader(Constants.BUNDLE_DOCURL) != null)
        {
            resource.put(Resource.DOCUMENTATION_URI, headers.getHeader(Constants.BUNDLE_DOCURL));
        }
        if (headers.getHeader(BUNDLE_SOURCE) != null)
        {
            resource.put(Resource.SOURCE_URI, headers.getHeader(BUNDLE_SOURCE));
        }

        doCategories(resource, headers);
        doBundle(resource, headers);
        doImportExportServices(resource, headers);
        doFragment(resource, headers);
        doRequires(resource, headers);
        doExports(resource, headers);
        doImports(resource, headers);
        doExecutionEnvironment(resource, headers);
    }

    private static void doCategories(ResourceImpl resource, Headers headers)
    {
        Clause[] clauses = Parser.parseHeader(headers.getHeader(Constants.BUNDLE_CATEGORY));
        for (int i = 0; clauses != null && i < clauses.length; i++)
        {
            resource.addCategory(clauses[i].getName());
        }
    }

    private static void doImportExportServices(ResourceImpl resource, Headers headers)
    {
        Clause[] imports = Parser.parseHeader(headers.getHeader(Constants.IMPORT_SERVICE));
        for (int i = 0; imports != null && i < imports.length; i++) {
            RequirementImpl ri = new RequirementImpl(Capability.SERVICE);
            ri.setFilter(createServiceFilter(imports[i]));
            ri.addText("Import Service " + imports[i].getName());

            String avail = imports[i].getDirective("availability");
            String mult = imports[i].getDirective("multiple");
            ri.setOptional("optional".equalsIgnoreCase(avail));
            ri.setMultiple(!"false".equalsIgnoreCase(mult));
            resource.addRequire(ri);
        }

        Clause[] exports = Parser.parseHeader(headers.getHeader(Constants.EXPORT_SERVICE));
        for (int i = 0; exports != null && i < exports.length; i++) {
            CapabilityImpl cap = createServiceCapability(exports[i]);
            resource.addCapability(cap);
        }
    }

    private static String createServiceFilter(Clause clause) {
        String f = clause.getAttribute("filter");
		StringBuffer filter = new StringBuffer();
        if (f != null) {
            filter.append("(&");
        }
        filter.append("(");
        filter.append(Capability.SERVICE);
        filter.append("=");
		filter.append(clause.getName());
		filter.append(")");
        if (f != null) {
            if (!f.startsWith("("))
            {
                filter.append("(").append(f).append(")");
            }
            else
            {
                filter.append(f);
            }
            filter.append(")");
        }
		return filter.toString();
    }

    private static CapabilityImpl createServiceCapability(Clause clause) {
        CapabilityImpl capability = new CapabilityImpl(Capability.SERVICE);
        capability.addProperty(Capability.SERVICE, clause.getName());
        Attribute[] attributes = clause.getAttributes();
        for (int i = 0; attributes != null && i < attributes.length; i++)
        {
            capability.addProperty(attributes[i].getName(), attributes[i].getValue());
        }
        return capability;
    }

    private static void doFragment(ResourceImpl resource, Headers headers)
    {
        // Check if we are a fragment
        Clause[] clauses = Parser.parseHeader(headers.getHeader(Constants.FRAGMENT_HOST));
        if (clauses != null && clauses.length == 1)
        {
            // We are a fragment, create a requirement
            // to our host.
            RequirementImpl r = new RequirementImpl(Capability.BUNDLE);
            StringBuffer sb = new StringBuffer();
            sb.append("(&(symbolicname=");
            sb.append(clauses[0].getName());
            sb.append(")");
            appendVersion(sb, VersionRange.parseVersionRange(clauses[0].getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE)));
            sb.append(")");
            r.setFilter(sb.toString());
            r.addText("Required Host " + clauses[0].getName());
            r.setExtend(true);
            r.setOptional(false);
            r.setMultiple(false);
            resource.addRequire(r);

            // And insert a capability that we are available
            // as a fragment. ### Do we need that with extend?
            CapabilityImpl capability = new CapabilityImpl(Capability.FRAGMENT);
            capability.addProperty("host", clauses[0].getName());
            capability.addProperty("version", Property.VERSION, getVersion(clauses[0]));
            resource.addCapability(capability);
        }
    }

    private static void doRequires(ResourceImpl resource, Headers headers)
    {
        Clause[] clauses = Parser.parseHeader(headers.getHeader(Constants.REQUIRE_BUNDLE));
        for (int i = 0; clauses != null && i < clauses.length; i++) {
            RequirementImpl r = new RequirementImpl(Capability.BUNDLE);

            VersionRange v = VersionRange.parseVersionRange(clauses[i].getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE));

            StringBuffer sb = new StringBuffer();
            sb.append("(&(symbolicname=");
            sb.append(clauses[i].getName());
            sb.append(")");
            appendVersion(sb, v);
            sb.append(")");
            r.setFilter(sb.toString());

            r.addText("Require Bundle " + clauses[i].getName() + "; " + v);
            r.setOptional(Constants.RESOLUTION_OPTIONAL.equalsIgnoreCase(clauses[i].getDirective(Constants.RESOLUTION_DIRECTIVE)));
            resource.addRequire(r);
        }
    }

    private static void doBundle(ResourceImpl resource, Headers headers) {
        CapabilityImpl capability = new CapabilityImpl(Capability.BUNDLE);
        capability.addProperty(Resource.SYMBOLIC_NAME, getSymbolicName(headers));
        if (headers.getHeader(Constants.BUNDLE_NAME) != null)
        {
            capability.addProperty(Resource.PRESENTATION_NAME, headers.getHeader(Constants.BUNDLE_NAME));
        }
        capability.addProperty(Resource.VERSION, Property.VERSION, getVersion(headers));
        capability.addProperty(Resource.MANIFEST_VERSION, getManifestVersion(headers));
        resource.addCapability(capability);
    }

    private static void doExports(ResourceImpl resource, Headers headers)
    {
        Clause[] clauses = Parser.parseHeader(headers.getHeader(Constants.EXPORT_PACKAGE));
        for (int i = 0; clauses != null && i < clauses.length; i++)
        {
            CapabilityImpl capability = createCapability(Capability.PACKAGE, clauses[i]);
            resource.addCapability(capability);
        }
    }

    private static CapabilityImpl createCapability(String name, Clause clause)
    {
        CapabilityImpl capability = new CapabilityImpl(name);
        capability.addProperty(name, clause.getName());
        capability.addProperty(Resource.VERSION, Property.VERSION, getVersion(clause));
        Attribute[] attributes = clause.getAttributes();
        for (int i = 0; attributes != null && i < attributes.length; i++)
        {
            String key = attributes[i].getName();
            if (key.equalsIgnoreCase(Constants.PACKAGE_SPECIFICATION_VERSION) || key.equalsIgnoreCase(Constants.VERSION_ATTRIBUTE))
            {
                continue;
            }
            else
            {
                String value = attributes[i].getValue();
                capability.addProperty(key, value);
            }
        }
        Directive[] directives = clause.getDirectives();
        for (int i = 0; directives != null && i < directives.length; i++)
        {
            String key = directives[i].getName();
            String value = directives[i].getValue();
            capability.addProperty(key + ":", value);
        }
        return capability;
    }

    private static void doImports(ResourceImpl resource, Headers headers)
    {
        Clause[] clauses = Parser.parseHeader(headers.getHeader(Constants.IMPORT_PACKAGE));
        for (int i = 0; clauses != null && i < clauses.length; i++)
        {
            RequirementImpl requirement = new RequirementImpl(Capability.PACKAGE);

            createImportFilter(requirement, Capability.PACKAGE, clauses[i]);
            requirement.addText("Import package " + clauses[i]);
            requirement.setOptional(Constants.RESOLUTION_OPTIONAL.equalsIgnoreCase(clauses[i].getDirective(Constants.RESOLUTION_DIRECTIVE)));
            resource.addRequire(requirement);
        }
    }

    private static void createImportFilter(RequirementImpl requirement, String name, Clause clause)
    {
        StringBuffer filter = new StringBuffer();
        filter.append("(&(");
        filter.append(name);
        filter.append("=");
        filter.append(clause.getName());
        filter.append(")");
        appendVersion(filter, getVersionRange(clause));
        Attribute[] attributes = clause.getAttributes();
        Set attrs = doImportPackageAttributes(requirement, filter, attributes);

        // The next code is using the subset operator
        // to check mandatory attributes, it seems to be
        // impossible to rewrite. It must assert that whateber
        // is in mandatory: must be in any of the attributes.
        // This is a fundamental shortcoming of the filter language.
        if (attrs.size() > 0)
        {
            String del = "";
            filter.append("(mandatory:<*");
            for (Iterator i = attrs.iterator(); i.hasNext();)
            {
                filter.append(del);
                filter.append(i.next());
                del = ", ";
            }
            filter.append(")");
        }
        filter.append(")");
        requirement.setFilter(filter.toString());
    }

    private static Set doImportPackageAttributes(RequirementImpl requirement, StringBuffer filter, Attribute[] attributes)
    {
        HashSet set = new HashSet();
        for (int i = 0; attributes != null && i < attributes.length; i++)
        {
            String name = attributes[i].getName();
            String value = attributes[i].getValue();
            if (name.equalsIgnoreCase(Constants.PACKAGE_SPECIFICATION_VERSION) || name.equalsIgnoreCase(Constants.VERSION_ATTRIBUTE))
            {
                continue;
            }
            else if (name.equalsIgnoreCase(Constants.RESOLUTION_DIRECTIVE + ":"))
            {
                requirement.setOptional(Constants.RESOLUTION_OPTIONAL.equalsIgnoreCase(value));
            }
            if (name.endsWith(":"))
            {
                // Ignore
            }
            else
            {
                filter.append("(");
                filter.append(name);
                filter.append("=");
                filter.append(value);
                filter.append(")");
                set.add(name);
            }
        }
        return set;
    }

    private static void doExecutionEnvironment(ResourceImpl resource, Headers headers)
    {
        Clause[] clauses = Parser.parseHeader(headers.getHeader(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
        if (clauses != null && clauses.length > 0)
        {
            StringBuffer sb = new StringBuffer();
            sb.append("(|");
            for (int i = 0; i < clauses.length; i++)
            {
                sb.append("(");
                sb.append(Capability.EXECUTIONENVIRONMENT);
                sb.append("=");
                sb.append(clauses[i].getName());
                sb.append(")");
            }
            sb.append(")");
            RequirementImpl req = new RequirementImpl(Capability.EXECUTIONENVIRONMENT);
            req.setFilter(sb.toString());
            req.addText("Execution Environment " + sb.toString());
            resource.addRequire(req);
        }
    }

    private static String getVersion(Clause clause)
    {
        String v = clause.getAttribute(Constants.VERSION_ATTRIBUTE);
        if (v == null)
        {
            v = clause.getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION);
        }
        if (v == null)
        {
            v = clause.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE);
        }
        return VersionCleaner.clean(v);
    }

    private static VersionRange getVersionRange(Clause clause)
    {
        String v = clause.getAttribute(Constants.VERSION_ATTRIBUTE);
        if (v == null)
        {
            v = clause.getAttribute(Constants.PACKAGE_SPECIFICATION_VERSION);
        }
        if (v == null)
        {
            v = clause.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE);
        }
        return VersionRange.parseVersionRange(v);
    }

    private static String getSymbolicName(Headers headers)
    {
        String bsn = headers.getHeader(Constants.BUNDLE_SYMBOLICNAME);
        if (bsn == null)
        {
            bsn = headers.getHeader(Constants.BUNDLE_NAME);
            if (bsn == null)
            {
                bsn = "Untitled-" + headers.hashCode();
            }
        }
        Clause[] clauses = Parser.parseHeader(bsn);
        return clauses[0].getName();
    }

    private static String getVersion(Headers headers)
    {
        String v = headers.getHeader(Constants.BUNDLE_VERSION);
        return VersionCleaner.clean(v);
    }

    private static String getManifestVersion(Headers headers)
    {
        String v = headers.getHeader(Constants.BUNDLE_MANIFESTVERSION);
        if (v == null)
        {
            v = "1";
        }
        return v;
    }

    private static void appendVersion(StringBuffer filter, VersionRange version)
    {
        if (version != null)
        {
            if ( !version.isOpenFloor() )
            {
                if ( !Version.emptyVersion.equals(version.getFloor()) )
                {
                    filter.append("(");
                    filter.append(Constants.VERSION_ATTRIBUTE);
                    filter.append(">=");
                    filter.append(version.getFloor());
                    filter.append(")");
                }
            }
            else
            {
                filter.append("(!(");
                filter.append(Constants.VERSION_ATTRIBUTE);
                filter.append("<=");
                filter.append(version.getFloor());
                filter.append("))");
            }

            if (!VersionRange.INFINITE_VERSION.equals(version.getCeiling()))
            {
                if ( !version.isOpenCeiling() )
                {
                    filter.append("(");
                    filter.append(Constants.VERSION_ATTRIBUTE);
                    filter.append("<=");
                    filter.append(version.getCeiling());
                    filter.append(")");
                }
                else
                {
                    filter.append("(!(");
                    filter.append(Constants.VERSION_ATTRIBUTE);
                    filter.append(">=");
                    filter.append(version.getCeiling());
                    filter.append("))");
                }
            }
        }
    }

    interface Headers
    {
        String getHeader(String name);
    }

    public Repository readRepository(String xml) throws Exception
    {
        try
        {
            return readRepository(new StringReader(xml));
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public Repository readRepository(Reader reader) throws Exception
    {
        return RepositoryParser.getParser().parseRepository(reader);
    }

    public Resource readResource(String xml) throws Exception
    {
        try
        {
            return readResource(new StringReader(xml));
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public Resource readResource(Reader reader) throws Exception
    {
        return RepositoryParser.getParser().parseResource(reader);
    }

    public Capability readCapability(String xml) throws Exception
    {
        try
        {
            return readCapability(new StringReader(xml));
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public Capability readCapability(Reader reader) throws Exception
    {
        return RepositoryParser.getParser().parseCapability(reader);
    }

    public Requirement readRequirement(String xml) throws Exception
    {
        try
        {
            return readRequirement(new StringReader(xml));
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public Requirement readRequirement(Reader reader) throws Exception
    {
        return RepositoryParser.getParser().parseRequirement(reader);
    }

    public Property readProperty(String xml) throws Exception
    {
        try
        {
            return readProperty(new StringReader(xml));
        }
        catch (IOException e)
        {
            IllegalStateException ex = new IllegalStateException(e);
            ex.initCause(e);
            throw ex;
        }
    }

    public Property readProperty(Reader reader) throws Exception
    {
        return RepositoryParser.getParser().parseProperty(reader);
    }
}
