/*
 * 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.ipojo.bnd;

import static java.lang.String.format;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.apache.felix.ipojo.manipulator.ResourceStore;
import org.apache.felix.ipojo.manipulator.ResourceVisitor;
import org.apache.felix.ipojo.manipulator.render.MetadataRenderer;
import org.apache.felix.ipojo.manipulator.util.Constants;
import org.apache.felix.ipojo.manipulator.util.Metadatas;
import org.apache.felix.ipojo.manipulator.util.Streams;
import org.apache.felix.ipojo.metadata.Element;

import aQute.bnd.header.Attrs;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Resource;
import aQute.service.reporter.Reporter;

public class BndJarResourceStore implements ResourceStore {

    private Analyzer m_analyzer;
    private Reporter m_reporter;

    private MetadataRenderer m_renderer = new MetadataRenderer();

    private List<Element> m_metadata;
    private boolean m_includeEmbedComponents;

    public BndJarResourceStore(Analyzer analyzer, Reporter reporter) {
        m_metadata = new ArrayList<Element>();
        m_analyzer = analyzer;
        m_reporter = reporter;
    }

    public byte[] read(String path) throws IOException {

        // Find the resource either in the global jar or in one of the embed dependencies
        Resource resource = m_analyzer.getJar().getResource(path);
        if (resource == null) {
            Jar embed = findJar(path);
            if (embed == null) {
                throw new IOException(format("Cannot find resource %s in jar and classpath", path));
            }
            resource = embed.getResource(path);
        }
        InputStream is = null;
        try {
            is = resource.openInputStream();
        } catch (Exception e) {
            throw new IOException("Cannot read " + path);
        }
        return Streams.readBytes(is);
    }

    public void accept(ResourceVisitor visitor) {

        try {
            // Collect all annotated classes
            Collection<Clazz> classes = m_analyzer.getClasses("",
                    Clazz.QUERY.CLASSANNOTATIONS.name());

            classes = filter(classes);

            // Iterates over discovered resources
            for (Clazz clazz : classes) {
                visitor.visit(clazz.getAbsolutePath());
            }
        } catch (Exception e) {
            m_reporter.error("Cannot find iPOJO annotated types: " + e.getMessage());
        }
    }

    private Collection<Clazz> filter(Collection<Clazz> classes) throws Exception {
        Set<Clazz> manipulable = new HashSet<Clazz>();
        for (Clazz clazz : classes) {

            // If it is i the main jar, simply use it
            if (m_analyzer.getJar().getResource(clazz.getAbsolutePath()) != null) {
                manipulable.add(clazz);
                continue;
            }

            if (m_includeEmbedComponents) {
                // Otherwise ...
                // Try to see if it is in an embed dependencies
                Jar jar = findJar(clazz.getAbsolutePath());
                if (jar == null) {
                    m_reporter.error("Resource for class %s not found in classpath", clazz.getFQN());
                    continue;
                }

                // Is it a Bundle ?
                if (jar.getBsn() != null) {
                    // OSGi Bundle case

                    // Check if the bundle was manipulated before
                    Attributes attr = jar.getManifest().getMainAttributes();
                    if (Manifests.getComponents(attr) != null) {
                        // Bundle has been previously manipulated
                        // TODO We should ignore the resource since it was already manipulated
                        // TODO But we should also merge its IPOJO-Components header
                    } else {
                        // Bundle was not manipulated
                        manipulable.add(clazz);
                    }

                } else  {
                    // Simple Jar file with iPOJO annotations
                    m_reporter.warning("Class %s found in a non-Bundle archive %s", clazz.getFQN(), jar.getName());
                    continue;
                }
            } else {
                m_reporter.warning("Embed components are excluded, Component %s will not be manipulated", clazz.getFQN());
            }
        }
        return manipulable;
    }

    private Jar findJar(String path) {
        for (Jar jar : m_analyzer.getClasspath()) {
            if (jar.getResource(path) != null) {
                return jar;
            }
        }
        return null;
    }

    public void open() throws IOException {
        // nothing to do
    }

    public void writeMetadata(Element metadata) {
        m_metadata.add(metadata);

        // Find referred packages and add them into Bnd
        for (String referred : Metadatas.findReferredPackages(metadata)) {
            if (!m_analyzer.getReferred().containsFQN(referred)) {
                // The given package is not referred ATM
                m_analyzer.getReferred().put(m_analyzer.getPackageRef(referred),
                                             new Attrs());
            }
        }

        // IPOJO-Components will be written during the close method.
    }

    public void write(String resourcePath, byte[] resource) throws IOException {
        Jar jar = m_analyzer.getJar();
        jar.putResource(resourcePath, new ByteArrayResource(resource));
    }

    public void close() throws IOException {

        // Write the iPOJO header (including manipulation metadata)
        StringBuilder builder = new StringBuilder();

        if (m_includeEmbedComponents) {
            // Incorporate metadata of embed dependencies (if any)
            for (Jar jar : m_analyzer.getClasspath()) {
                try {
                    Manifest manifest = jar.getManifest();
                    Attributes main = manifest.getMainAttributes();
                    String components = Manifests.getComponents(main);
                    if (components != null) {
                        m_reporter.trace("Merging components from %s", jar.getName());
                        builder.append(components);
                    }
                } catch (Exception e) {
                    m_reporter.warning("Cannot open MANIFEST of %s", jar.getName());
                }
            }
        }

        for (Element metadata : m_metadata) {
            builder.append(m_renderer.render(metadata));
        }

        if (builder.length() != 0) {
            m_analyzer.setProperty("IPOJO-Components", builder.toString());
        }

        // Add some mandatory imported packages
        Attrs version = new Attrs();
        version.put("version:Version", Constants.getPackageImportClause());

        if (!m_analyzer.getReferred().containsFQN("org.apache.felix.ipojo")) {
            m_analyzer.getReferred().put(m_analyzer.getPackageRef("org.apache.felix.ipojo"),
                                         version);
        }
        if (!m_analyzer.getReferred().containsFQN("org.apache.felix.ipojo.architecture")) {
            m_analyzer.getReferred().put(m_analyzer.getPackageRef("org.apache.felix.ipojo.architecture"),
                                         version);
        }
        if (!m_analyzer.getReferred().containsFQN("org.osgi.service.cm")) {
            Attrs cm = new Attrs();
            cm.put("version:Version", "1.2");
            m_analyzer.getReferred().put(m_analyzer.getPackageRef("org.osgi.service.cm"),
                                         cm);
        }
        if (!m_analyzer.getReferred().containsFQN("org.osgi.service.log")) {
            Attrs log = new Attrs();
            log.put("version:Version", "1.3");
            m_analyzer.getReferred().put(m_analyzer.getPackageRef("org.osgi.service.log"),
                                         log);
        }


    }

    public void setIncludeEmbedComponents(boolean excludeEmbedComponents) {
        m_includeEmbedComponents = excludeEmbedComponents;
    }
}

@@ -3692,16 +3692,17 @@
  it is i
+n
  the mai

Fix typo